summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp20
-rw-r--r--Android.bp23
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--Ravenwood.bp1
-rw-r--r--TEST_MAPPING6
-rw-r--r--apct-tests/perftests/core/src/android/input/OWNERS3
-rw-r--r--apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java278
-rw-r--r--apct-tests/perftests/core/src/android/view/ViewPerfTest.java2
-rw-r--r--apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt9
-rw-r--r--apct-tests/perftests/input/Android.bp44
-rw-r--r--apct-tests/perftests/input/AndroidManifest.xml37
-rw-r--r--apct-tests/perftests/input/OWNERS2
-rw-r--r--apct-tests/perftests/input/src/android/input/InputPerfRunPrecondition.kt22
-rw-r--r--apct-tests/perftests/input/src/android/input/MotionPredictorBenchmark.kt (renamed from apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt)78
-rw-r--r--apct-tests/perftests/input/src/android/input/TouchPerfTest.kt66
-rw-r--r--apct-tests/perftests/input/src/android/input/VelocityTrackerBenchmarkTest.kt (renamed from apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt)63
-rw-r--r--apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt168
-rw-r--r--apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt4
-rw-r--r--apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt27
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java6
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java34
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java96
-rw-r--r--boot/boot-image-profile-extra.txt7
-rw-r--r--boot/preloaded-classes1
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java6
-rw-r--r--cmds/bootanimation/BootAnimation.cpp2
-rw-r--r--cmds/svc/src/com/android/commands/svc/UsbCommand.java5
-rw-r--r--config/preloaded-classes1
-rw-r--r--core/api/current.txt25
-rw-r--r--core/api/system-current.txt10
-rw-r--r--core/api/test-current.txt4
-rw-r--r--core/java/Android.bp9
-rw-r--r--core/java/android/animation/AnimationHandler.java16
-rw-r--r--core/java/android/animation/Animator.java33
-rw-r--r--core/java/android/animation/AnimatorSet.java7
-rw-r--r--core/java/android/animation/ValueAnimator.java1
-rw-r--r--core/java/android/app/Activity.java8
-rw-r--r--core/java/android/app/ActivityManager.java35
-rw-r--r--core/java/android/app/ActivityManagerInternal.java8
-rw-r--r--core/java/android/app/ActivityThread.java27
-rw-r--r--core/java/android/app/AppCompatTaskInfo.java43
-rw-r--r--core/java/android/app/AppOpsManager.java11
-rw-r--r--core/java/android/app/AutomaticZenRule.java12
-rw-r--r--core/java/android/app/ContextImpl.java10
-rw-r--r--core/java/android/app/IActivityManager.aidl4
-rw-r--r--core/java/android/app/Notification.java226
-rw-r--r--core/java/android/app/TaskInfo.java6
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java5
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManagerHelper.java4
-rw-r--r--core/java/android/app/backup/BackupManagerMonitor.java12
-rw-r--r--core/java/android/app/jank/JankDataProcessor.java4
-rw-r--r--core/java/android/app/jank/JankTracker.java75
-rw-r--r--core/java/android/app/jank/TEST_MAPPING10
-rw-r--r--core/java/android/app/jank/flags.aconfig10
-rw-r--r--core/java/android/app/notification.aconfig31
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java2
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig8
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/Intent.java10
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java69
-rw-r--r--core/java/android/content/pm/TEST_MAPPING44
-rw-r--r--core/java/android/content/pm/UserInfo.java5
-rw-r--r--core/java/android/content/pm/multiuser.aconfig9
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java4
-rw-r--r--core/java/android/database/sqlite/flags.aconfig9
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java8
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java13
-rw-r--r--core/java/android/hardware/display/DisplayManager.java5
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java10
-rw-r--r--core/java/android/hardware/input/IKeyGestureHandler.aidl11
-rw-r--r--core/java/android/hardware/input/InputManager.java7
-rw-r--r--core/java/android/hardware/input/InputManagerGlobal.java17
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java6
-rw-r--r--core/java/android/hardware/usb/IUsbManagerInternal.aidl (renamed from packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt)16
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java12
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java28
-rw-r--r--core/java/android/os/ExternalVibration.java12
-rw-r--r--core/java/android/os/LegacyMessageQueue/MessageQueue.java8
-rw-r--r--core/java/android/os/Parcel.java78
-rw-r--r--core/java/android/os/Process.java7
-rw-r--r--core/java/android/os/TestLooperManager.java10
-rw-r--r--core/java/android/os/UserManager.java29
-rw-r--r--core/java/android/os/flags.aconfig8
-rw-r--r--core/java/android/os/health/SystemHealthManager.java10
-rw-r--r--core/java/android/os/instrumentation/MethodDescriptorParser.java10
-rw-r--r--core/java/android/os/vibrator/flags.aconfig11
-rw-r--r--core/java/android/permission/PermissionManager.java6
-rw-r--r--core/java/android/permission/flags.aconfig33
-rw-r--r--core/java/android/preference/PreferenceScreen.java18
-rw-r--r--core/java/android/provider/Settings.java195
-rw-r--r--core/java/android/provider/Telephony.java67
-rw-r--r--core/java/android/security/advancedprotection/AdvancedProtectionManager.java2
-rw-r--r--core/java/android/security/flags.aconfig7
-rw-r--r--core/java/android/service/chooser/flags.aconfig10
-rw-r--r--core/java/android/service/dreams/DreamService.java101
-rw-r--r--core/java/android/service/notification/Adjustment.java2
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java2
-rw-r--r--core/java/android/service/notification/TEST_MAPPING5
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java24
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java4
-rw-r--r--core/java/android/service/voice/HotwordDetectionService.java4
-rw-r--r--core/java/android/service/voice/ISandboxedDetectionService.aidl6
-rw-r--r--core/java/android/service/voice/VisualQueryDetectionService.java4
-rw-r--r--core/java/android/text/Layout.java9
-rw-r--r--core/java/android/util/MapCollections.java37
-rw-r--r--core/java/android/view/Choreographer.java6
-rw-r--r--core/java/android/view/Display.java14
-rw-r--r--core/java/android/view/DisplayInfo.java3
-rw-r--r--core/java/android/view/InsetsController.java6
-rw-r--r--core/java/android/view/LayoutInflater.java5
-rw-r--r--core/java/android/view/ListenerWrapper.java56
-rw-r--r--core/java/android/view/NotificationTopLineView.java7
-rw-r--r--core/java/android/view/ViewConfiguration.java231
-rw-r--r--core/java/android/view/ViewRootImpl.java33
-rw-r--r--core/java/android/view/WindowManager.java38
-rw-r--r--core/java/android/view/WindowManagerGlobal.java33
-rw-r--r--core/java/android/view/XrWindowProperties.java159
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java42
-rw-r--r--core/java/android/view/contentcapture/flags/content_capture_flags.aconfig1
-rw-r--r--core/java/android/view/flags/view_flags.aconfig8
-rw-r--r--core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java10
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java38
-rw-r--r--core/java/android/view/inputmethod/RemoteInputConnectionImpl.java188
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig10
-rw-r--r--core/java/android/view/inspector/WindowInspector.java24
-rw-r--r--core/java/android/view/translation/ListenerGroup.java87
-rw-r--r--core/java/android/webkit/LegacyErrorStrings.java2
-rw-r--r--core/java/android/webkit/WebIconDatabase.java2
-rw-r--r--core/java/android/widget/ListView.java2
-rw-r--r--core/java/android/window/BackMotionEvent.java22
-rw-r--r--core/java/android/window/BackTouchTracker.java9
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java23
-rw-r--r--core/java/android/window/DesktopModeFlags.java14
-rw-r--r--core/java/android/window/IWindowOrganizerController.aidl11
-rw-r--r--core/java/android/window/ImeOnBackInvokedDispatcher.java6
-rw-r--r--core/java/android/window/WindowContainerTransaction.java101
-rw-r--r--core/java/android/window/WindowOrganizer.java21
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig8
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig114
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig63
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig32
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java11
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java76
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java20
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserContentManager.java56
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialog.java37
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java6
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java190
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistoryIterator.java74
-rw-r--r--core/java/com/android/internal/policy/DecorView.java130
-rw-r--r--core/java/com/android/internal/policy/DesktopModeCompatUtils.java60
-rw-r--r--core/java/com/android/internal/policy/SystemBarUtils.java17
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java20
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl8
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java12
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java85
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java1
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java7
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java13
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java1
-rw-r--r--core/java/com/android/internal/widget/NotificationActionListLayout.java19
-rw-r--r--core/java/com/android/internal/widget/NotificationExpandButton.java10
-rw-r--r--core/java/com/android/internal/widget/PeopleHelper.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java144
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/PaintContext.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java60
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java240
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/HapticFeedback.java132
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathCombine.java176
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java23
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java18
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java314
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java37
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java146
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java21
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java18
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java6
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp13
-rw-r--r--core/jni/android_media_ImageWriter.cpp2
-rw-r--r--core/jni/android_os_Parcel.cpp87
-rw-r--r--core/jni/android_view_InputChannel.cpp29
-rw-r--r--core/jni/android_view_Surface.cpp8
-rw-r--r--core/jni/android_view_SurfaceSession.cpp8
-rw-r--r--core/jni/android_view_TextureView.cpp2
-rw-r--r--core/jni/com_android_internal_content_FileSystemUtils.cpp21
-rw-r--r--core/jni/com_android_internal_content_FileSystemUtils.h14
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp12
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp10
-rw-r--r--core/jni/com_google_android_gles_jni_EGLImpl.cpp2
-rw-r--r--core/jni/platform/host/HostRuntime.cpp14
-rw-r--r--core/proto/android/os/incident.proto7
-rw-r--r--core/proto/android/providers/settings/secure.proto16
-rw-r--r--core/proto/android/providers/settings/system.proto11
-rw-r--r--core/proto/android/server/Android.bp28
-rw-r--r--core/proto/android/server/bluetooth_manager_service.proto49
-rw-r--r--core/proto/android/server/windowmanagerservice.proto28
-rw-r--r--core/res/res/drawable/accessibility_autoclick_scroll_down.xml25
-rw-r--r--core/res/res/drawable/accessibility_autoclick_scroll_exit.xml25
-rw-r--r--core/res/res/drawable/accessibility_autoclick_scroll_left.xml25
-rw-r--r--core/res/res/drawable/accessibility_autoclick_scroll_right.xml25
-rw-r--r--core/res/res/drawable/accessibility_autoclick_scroll_up.xml (renamed from packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml)21
-rw-r--r--core/res/res/layout/accessibility_autoclick_scroll_panel.xml92
-rw-r--r--core/res/res/layout/accessibility_autoclick_type_panel.xml21
-rw-r--r--core/res/res/layout/notification_2025_action_list.xml85
-rw-r--r--core/res/res/layout/notification_2025_conversation_icon_container.xml3
-rw-r--r--core/res/res/layout/notification_2025_expand_button.xml1
-rw-r--r--core/res/res/layout/notification_2025_reply_history_container.xml14
-rw-r--r--core/res/res/layout/notification_2025_right_icon.xml31
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml27
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml43
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_conversation.xml46
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml27
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml48
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_base.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml4
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_base.xml12
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_picture.xml13
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_text.xml12
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_call.xml11
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_conversation.xml11
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_inbox.xml11
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_media.xml6
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml11
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_progress.xml12
-rw-r--r--core/res/res/layout/notification_2025_template_heads_up_base.xml6
-rw-r--r--core/res/res/layout/notification_2025_text.xml8
-rw-r--r--core/res/res/layout/notification_template_notification_progress_bar.xml1
-rw-r--r--core/res/res/values-af/strings.xml21
-rw-r--r--core/res/res/values-am/strings.xml21
-rw-r--r--core/res/res/values-ar/strings.xml21
-rw-r--r--core/res/res/values-as/strings.xml21
-rw-r--r--core/res/res/values-az/strings.xml21
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml21
-rw-r--r--core/res/res/values-be/strings.xml23
-rw-r--r--core/res/res/values-bg/strings.xml20
-rw-r--r--core/res/res/values-bn/strings.xml23
-rw-r--r--core/res/res/values-bs/strings.xml21
-rw-r--r--core/res/res/values-ca/strings.xml21
-rw-r--r--core/res/res/values-cs/strings.xml21
-rw-r--r--core/res/res/values-da/strings.xml21
-rw-r--r--core/res/res/values-de/strings.xml21
-rw-r--r--core/res/res/values-el/strings.xml21
-rw-r--r--core/res/res/values-en-rAU/strings.xml20
-rw-r--r--core/res/res/values-en-rCA/strings.xml11
-rw-r--r--core/res/res/values-en-rGB/strings.xml20
-rw-r--r--core/res/res/values-en-rIN/strings.xml20
-rw-r--r--core/res/res/values-es-rUS/strings.xml20
-rw-r--r--core/res/res/values-es/strings.xml21
-rw-r--r--core/res/res/values-et/strings.xml29
-rw-r--r--core/res/res/values-eu/strings.xml21
-rw-r--r--core/res/res/values-fa/strings.xml20
-rw-r--r--core/res/res/values-fi/strings.xml21
-rw-r--r--core/res/res/values-fr-rCA/strings.xml20
-rw-r--r--core/res/res/values-fr/strings.xml21
-rw-r--r--core/res/res/values-gl/strings.xml21
-rw-r--r--core/res/res/values-gu/strings.xml18
-rw-r--r--core/res/res/values-hi/strings.xml21
-rw-r--r--core/res/res/values-hr/strings.xml37
-rw-r--r--core/res/res/values-hu/strings.xml20
-rw-r--r--core/res/res/values-hy/strings.xml20
-rw-r--r--core/res/res/values-in/strings.xml21
-rw-r--r--core/res/res/values-is/strings.xml22
-rw-r--r--core/res/res/values-it/strings.xml24
-rw-r--r--core/res/res/values-iw/strings.xml21
-rw-r--r--core/res/res/values-ja/strings.xml20
-rw-r--r--core/res/res/values-ka/strings.xml20
-rw-r--r--core/res/res/values-kk/strings.xml21
-rw-r--r--core/res/res/values-km/strings.xml21
-rw-r--r--core/res/res/values-kn/strings.xml20
-rw-r--r--core/res/res/values-ko/strings.xml21
-rw-r--r--core/res/res/values-ky/strings.xml24
-rw-r--r--core/res/res/values-lo/strings.xml21
-rw-r--r--core/res/res/values-lt/strings.xml20
-rw-r--r--core/res/res/values-lv/strings.xml21
-rw-r--r--core/res/res/values-mk/strings.xml23
-rw-r--r--core/res/res/values-ml/strings.xml20
-rw-r--r--core/res/res/values-mn/strings.xml23
-rw-r--r--core/res/res/values-mr/strings.xml61
-rw-r--r--core/res/res/values-ms/strings.xml21
-rw-r--r--core/res/res/values-my/strings.xml21
-rw-r--r--core/res/res/values-nb/strings.xml21
-rw-r--r--core/res/res/values-ne/strings.xml21
-rw-r--r--core/res/res/values-nl/strings.xml21
-rw-r--r--core/res/res/values-or/strings.xml21
-rw-r--r--core/res/res/values-pa/strings.xml21
-rw-r--r--core/res/res/values-pl/strings.xml23
-rw-r--r--core/res/res/values-pt-rBR/strings.xml21
-rw-r--r--core/res/res/values-pt-rPT/strings.xml22
-rw-r--r--core/res/res/values-pt/strings.xml21
-rw-r--r--core/res/res/values-ro/strings.xml23
-rw-r--r--core/res/res/values-ru/strings.xml21
-rw-r--r--core/res/res/values-si/strings.xml23
-rw-r--r--core/res/res/values-sk/strings.xml20
-rw-r--r--core/res/res/values-sl/strings.xml20
-rw-r--r--core/res/res/values-sq/strings.xml29
-rw-r--r--core/res/res/values-sr/strings.xml21
-rw-r--r--core/res/res/values-sv/strings.xml22
-rw-r--r--core/res/res/values-sw/strings.xml21
-rw-r--r--core/res/res/values-ta/strings.xml21
-rw-r--r--core/res/res/values-te/strings.xml20
-rw-r--r--core/res/res/values-th/strings.xml22
-rw-r--r--core/res/res/values-tl/strings.xml20
-rw-r--r--core/res/res/values-tr/strings.xml21
-rw-r--r--core/res/res/values-uk/strings.xml21
-rw-r--r--core/res/res/values-ur/strings.xml20
-rw-r--r--core/res/res/values-uz/strings.xml21
-rw-r--r--core/res/res/values-vi/strings.xml21
-rw-r--r--core/res/res/values-zh-rCN/strings.xml21
-rw-r--r--core/res/res/values-zh-rHK/strings.xml21
-rw-r--r--core/res/res/values-zh-rTW/strings.xml21
-rw-r--r--core/res/res/values-zu/strings.xml21
-rw-r--r--core/res/res/values/config.xml64
-rw-r--r--core/res/res/values/config_telephony.xml10
-rw-r--r--core/res/res/values/dimens.xml51
-rw-r--r--core/res/res/values/strings.xml16
-rw-r--r--core/res/res/values/styles.xml13
-rw-r--r--core/res/res/values/symbols.xml54
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java2
-rw-r--r--core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java4
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java38
-rw-r--r--core/tests/coretests/src/android/animation/ValueAnimatorTests.java33
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java10
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java6
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java403
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java6
-rw-r--r--core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java4
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java61
-rw-r--r--core/tests/coretests/src/android/provider/FontsContractTest.java4
-rw-r--r--core/tests/coretests/src/android/text/LayoutTest.java79
-rw-r--r--core/tests/coretests/src/android/util/ArrayMapTest.java51
-rw-r--r--core/tests/coretests/src/android/view/PendingInsetsControllerTest.java4
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java8
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java4
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java4
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java44
-rw-r--r--core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java3
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityTest.java2
-rw-r--r--core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java11
-rw-r--r--core/tests/coretests/src/android/window/BackTouchTrackerTest.kt2
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java15
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java51
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java39
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java8
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java4
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java4
-rw-r--r--core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java8
-rw-r--r--core/tests/vibrator/src/android/os/ExternalVibrationTest.java18
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java21
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java103
-rw-r--r--libs/WindowManager/Shell/aconfig/OWNERS3
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig19
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt1
-rw-r--r--libs/WindowManager/Shell/multivalentTests/Android.bp2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt73
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt6
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt8
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml6
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml31
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml22
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml1
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml9
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml9
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml33
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml8
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java7
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt86
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt23
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt67
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt17
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt14
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt60
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java34
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java223
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java94
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java135
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt101
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt147
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt806
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt259
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt174
-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/freeform/FreeformTaskTransitionObserver.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java139
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt100
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt5
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt84
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt44
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt44
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt53
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt53
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt53
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt53
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt6
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt59
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt62
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt10
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt16
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt17
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt168
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java99
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java88
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java163
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt119
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt75
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt383
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt168
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt55
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt95
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt1260
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt101
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt290
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt414
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt123
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java95
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java69
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt115
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java175
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt117
-rw-r--r--libs/androidfw/ResourceTypes.cpp94
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h15
-rw-r--r--libs/hostgraphics/HostBufferQueue.cpp1
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp2
-rw-r--r--libs/hwui/jni/Graphics.cpp13
-rw-r--r--location/java/android/location/GnssClock.java2
-rw-r--r--location/java/android/location/flags/location.aconfig15
-rw-r--r--media/java/Android.bp1
-rw-r--r--media/java/android/media/AudioManager.java21
-rw-r--r--media/java/android/media/IMediaRouter2.aidl3
-rw-r--r--media/java/android/media/IMediaRouter2Manager.aidl3
-rw-r--r--media/java/android/media/IMediaRouterService.aidl9
-rw-r--r--media/java/android/media/MediaCodecInfo.java145
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java17
-rw-r--r--media/java/android/media/MediaRouter.java12
-rw-r--r--media/java/android/media/MediaRouter2.java226
-rw-r--r--media/java/android/media/MediaRouter2Manager.java8
-rw-r--r--media/java/android/media/SuggestedDeviceInfo.aidl (renamed from packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt)7
-rw-r--r--media/java/android/media/SuggestedDeviceInfo.java235
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig18
-rw-r--r--media/java/android/media/quality/Android.bp64
-rw-r--r--media/java/android/media/quality/MediaQualityContract.java2
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java86
-rw-r--r--media/java/android/media/quality/SoundProfileHandle.java72
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl (renamed from media/java/android/media/quality/ActiveProcessingPicture.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl (renamed from media/java/android/media/quality/AmbientBacklightEvent.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl (renamed from media/java/android/media/quality/AmbientBacklightMetadata.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl (renamed from media/java/android/media/quality/AmbientBacklightSettings.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/IAmbientBacklightCallback.aidl (renamed from media/java/android/media/quality/IAmbientBacklightCallback.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl (renamed from media/java/android/media/quality/IMediaQualityManager.aidl)70
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/IPictureProfileCallback.aidl (renamed from media/java/android/media/quality/IPictureProfileCallback.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/ISoundProfileCallback.aidl (renamed from media/java/android/media/quality/ISoundProfileCallback.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl (renamed from media/java/android/media/quality/ParameterCapability.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl (renamed from media/java/android/media/quality/PictureProfile.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl (renamed from media/java/android/media/quality/PictureProfileHandle.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl (renamed from media/java/android/media/quality/SoundProfile.aidl)2
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/SoundProfileHandle.aidl (renamed from media/java/android/media/quality/SoundProfileHandle.aidl)5
-rw-r--r--media/java/android/media/quality/include/quality/MediaQualityManager.h127
-rw-r--r--media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl2
-rw-r--r--media/java/android/media/tv/extension/scan/IScanListener.aidl2
-rw-r--r--media/java/android/media/tv/extension/scan/IScanSession.aidl2
-rw-r--r--media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl7
-rw-r--r--media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl4
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java4
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java6
-rw-r--r--native/android/performance_hint.cpp2
-rw-r--r--native/android/surface_control.cpp2
-rw-r--r--packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java2
-rw-r--r--packages/CompanionDeviceManager/res/values-af/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-am/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ar/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-as/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-az/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-be/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-bg/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-bn/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-bs/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ca/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-cs/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-da/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-de/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-el/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rAU/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rCA/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rGB/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-en-rIN/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-es-rUS/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-es/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-et/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-fa/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-fi/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-fr/strings.xml8
-rw-r--r--packages/CompanionDeviceManager/res/values-gl/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-gu/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-hi/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-hr/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-hu/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-hy/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-in/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-is/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-it/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-iw/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ja/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ka/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-kk/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-km/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-kn/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ko/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ky/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-lo/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-lt/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-lv/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-mk/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ml/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-mn/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-mr/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ms/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-my/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-nb/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ne/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-nl/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-or/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-pa/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-pl/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-pt/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ro/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ru/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-si/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-sk/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-sl/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-sq/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-sr/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-sv/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-sw/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ta/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-te/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-th/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-tl/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-tr/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-uk/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-ur/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-uz/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-vi/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values-zu/strings.xml6
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java7
-rw-r--r--packages/DynamicSystemInstallationService/res/values-mr/strings.xml12
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt2
-rw-r--r--packages/ExternalStorageProvider/res/values-fa/strings.xml2
-rw-r--r--packages/PackageInstaller/TEST_MAPPING44
-rw-r--r--packages/PackageInstaller/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt28
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt24
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt69
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt191
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt31
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java65
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java2
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt1
-rw-r--r--packages/SettingsLib/IllustrationPreference/res/values/strings.xml6
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java30
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java4
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml6
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml6
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java20
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java21
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt38
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt50
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt25
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt1
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt34
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml4
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml (renamed from packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml)0
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java5
-rw-r--r--packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java14
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml8
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml8
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml6
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml8
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml6
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml8
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt17
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt60
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp59
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml (renamed from packages/SystemUI/res/drawable/qs_media_rec_scrim.xml)24
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties16
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt136
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/Android.bp (renamed from packages/SettingsLib/SpaPrivileged/tests/Android.bp)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/AndroidManifest.xml (renamed from packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/res/values/strings.xml (renamed from packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt)12
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt)8
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt)0
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt (renamed from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt)0
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig17
-rw-r--r--packages/SettingsLib/res/drawable/ic_news.xml19
-rw-r--r--packages/SettingsLib/res/drawable/ic_promotions.xml19
-rw-r--r--packages/SettingsLib/res/drawable/ic_recs.xml25
-rw-r--r--packages/SettingsLib/res/drawable/ic_social.xml19
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-b+sr+Latn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rAU/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rGB/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-en-rIN/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ja/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ka/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-km/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ml/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-ms/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-nl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pt-rBR/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-pt/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ru/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-si/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-tl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ur/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml2
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt (renamed from packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt)4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java117
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt90
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt78
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt300
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java6
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java8
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java10
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java2
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java10
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java11
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java95
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt124
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java21
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt7
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt178
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java12
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java36
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java9
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java14
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/OWNERS4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java25
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java26
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java1
-rw-r--r--packages/Shell/res/values-et/strings.xml2
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java24
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java2
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/BUILD_OWNERS22
-rw-r--r--packages/SystemUI/OWNERS3
-rw-r--r--packages/SystemUI/TEST_OWNERS3
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java2
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig10
-rw-r--r--packages/SystemUI/aconfig/predictive_back.aconfig10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig77
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt46
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt52
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt5
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt20
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt18
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt38
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt79
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt84
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt6
-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/DoNotDirectlyConstructKosmosTest.kt104
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt104
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt56
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt45
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt21
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt64
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt159
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt113
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt32
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/PagerDots.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt)4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt288
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt395
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt44
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt44
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt34
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt47
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt103
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt17
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt135
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt261
-rw-r--r--packages/SystemUI/compose/scene/tests/Android.bp1
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json654
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json644
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json544
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json444
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json354
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json724
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json324
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json244
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json634
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json614
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json544
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json444
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json374
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json714
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json314
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json244
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json68
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json48
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json26
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json4
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json4
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt34
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt519
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt310
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt16
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt10
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt36
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt14
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt17
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt46
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt3
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt116
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt19
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt48
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt14
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt5
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt55
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt277
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java119
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt201
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt158
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java)34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt122
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt240
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt120
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt109
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt173
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt165
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt119
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt147
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt155
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/NotificationMediaManagerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt)18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt137
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt168
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt100
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt90
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt88
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt105
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt211
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt118
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt134
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt80
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt69
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java124
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt114
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt193
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt375
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt276
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt88
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt160
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt)47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt383
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java339
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt104
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt66
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt)122
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt1046
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt1137
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt198
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt1077
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt385
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt142
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt517
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt113
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt69
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt7
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt12
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt20
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt65
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt132
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt (renamed from packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt)22
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt200
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt70
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt12
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt)5
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Main.java (renamed from packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java)0
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/UiBackground.java (renamed from packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/UiBackground.java)0
-rw-r--r--packages/SystemUI/res-keyguard/values-ar/strings.xml4
-rw-r--r--packages/SystemUI/res-keyguard/values-hi/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-iw/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml2
-rw-r--r--packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml35
-rw-r--r--packages/SystemUI/res/layout/bluetooth_device_item.xml6
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml3
-rw-r--r--packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml38
-rw-r--r--packages/SystemUI/res/layout/magic_action_button.xml2
-rw-r--r--packages/SystemUI/res/layout/media_output_dialog.xml27
-rw-r--r--packages/SystemUI/res/layout/media_recommendation_view.xml90
-rw-r--r--packages/SystemUI/res/layout/media_recommendations.xml75
-rw-r--r--packages/SystemUI/res/layout/notif_half_shelf.xml15
-rw-r--r--packages/SystemUI/res/layout/notif_half_shelf_row.xml15
-rw-r--r--packages/SystemUI/res/layout/notification_2025_info.xml37
-rw-r--r--packages/SystemUI/res/layout/notification_2025_smart_action_button.xml35
-rw-r--r--packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml36
-rw-r--r--packages/SystemUI/res/layout/notification_info.xml15
-rw-r--r--packages/SystemUI/res/layout/partial_conversation_info.xml15
-rw-r--r--packages/SystemUI/res/layout/promoted_notification_info.xml15
-rw-r--r--packages/SystemUI/res/layout/sidefps_view.xml1
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml6
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_top_section.xml2
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json2
-rw-r--r--packages/SystemUI/res/values-af/strings.xml32
-rw-r--r--packages/SystemUI/res/values-am/strings.xml32
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml38
-rw-r--r--packages/SystemUI/res/values-as/strings.xml32
-rw-r--r--packages/SystemUI/res/values-az/strings.xml32
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml23
-rw-r--r--packages/SystemUI/res/values-be/strings.xml32
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml19
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml34
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml29
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml20
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml32
-rw-r--r--packages/SystemUI/res/values-da/strings.xml32
-rw-r--r--packages/SystemUI/res/values-de/strings.xml32
-rw-r--r--packages/SystemUI/res/values-el/strings.xml23
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml28
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml9
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml28
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml28
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml18
-rw-r--r--packages/SystemUI/res/values-es/strings.xml34
-rw-r--r--packages/SystemUI/res/values-et/strings.xml32
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml33
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml22
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml32
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml31
-rw-r--r--packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml2
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml23
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml23
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml23
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml34
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml23
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml31
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml19
-rw-r--r--packages/SystemUI/res/values-in/strings.xml32
-rw-r--r--packages/SystemUI/res/values-is/strings.xml19
-rw-r--r--packages/SystemUI/res/values-it/strings.xml19
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml50
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml18
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml18
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml42
-rw-r--r--packages/SystemUI/res/values-km/strings.xml23
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml18
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml34
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml31
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml20
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml18
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml23
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml32
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml22
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml32
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml35
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml20
-rw-r--r--packages/SystemUI/res/values-my/strings.xml32
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml32
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml32
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml29
-rw-r--r--packages/SystemUI/res/values-or/strings.xml32
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml23
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml38
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml34
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml18
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml34
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml32
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml20
-rw-r--r--packages/SystemUI/res/values-si/strings.xml32
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml28
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml32
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml23
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml22
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml32
-rw-r--r--packages/SystemUI/res/values-sw/tiles_states_strings.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/dimens.xml5
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml32
-rw-r--r--packages/SystemUI/res/values-te/strings.xml18
-rw-r--r--packages/SystemUI/res/values-th/strings.xml28
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml21
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml32
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml22
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml32
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml34
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml34
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml23
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml23
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml32
-rw-r--r--packages/SystemUI/res/values/config.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml24
-rw-r--r--packages/SystemUI/res/values/strings.xml20
-rw-r--r--packages/SystemUI/res/values/styles.xml51
-rw-r--r--packages/SystemUI/res/values/tiles_states_strings.xml10
-rw-r--r--packages/SystemUI/res/xml/media_recommendations_collapsed.xml64
-rw-r--r--packages/SystemUI/res/xml/media_recommendations_expanded.xml71
-rw-r--r--packages/SystemUI/shared/Android.bp1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt11
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java306
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt270
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt18
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt20
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java15
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java70
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/KairosActivatable.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesChecker.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java112
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt170
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt148
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt404
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt223
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt170
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/NotificationMediaManager.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java)84
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt156
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt469
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt211
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java502
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt238
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java235
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java304
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt160
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt1273
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt376
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.kt229
-rw-r--r--packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt151
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt219
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt319
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt133
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OWNERS6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt168
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt171
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactory.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java151
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java262
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt8
-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/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt337
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt395
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt172
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt328
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt152
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt196
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java2
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json)0
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json)0
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json)11
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json)11
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json)0
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json)11
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json)11
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json)0
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json492
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json375
-rw-r--r--packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json375
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt386
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt159
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt835
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt100
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java383
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java66
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt61
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt107
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt102
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt54
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt)7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt2
-rw-r--r--packages/Vcn/service-b/Android.bp5
-rw-r--r--packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java20
-rw-r--r--ravenwood/Android.bp18
-rw-r--r--ravenwood/Framework.bp12
-rw-r--r--ravenwood/TEST_MAPPING18
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java10
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java22
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java2
-rwxr-xr-xravenwood/scripts/extract-last-soong-commands.py2
-rwxr-xr-xravenwood/scripts/list-ravenwood-tests.sh18
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt4
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodJdkPatchTest.java63
-rw-r--r--ravenwood/tests/resapk_test/Android.bp3
-rw-r--r--ravenwood/tests/resapk_test/apk/Android.bp8
-rw-r--r--ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml8
-rw-r--r--ravenwood/tests/resapk_test/apk/res/values/strings.xml5
-rw-r--r--ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java91
-rw-r--r--ravenwood/texts/ravenizer-standard-options.txt13
-rw-r--r--ravenwood/texts/ravenwood-standard-annotations.txt37
-rw-r--r--ravenwood/texts/ravenwood-standard-options.txt38
-rw-r--r--ravenwood/tools/hoststubgen/Android.bp15
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Exceptions.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt)11
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt210
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt177
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenErrors.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenLogger.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt)22
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt)2
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt)2
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AnnotationBasedFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ConstantFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DelegatingFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt)27
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterRemapper.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt)38
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/InMemoryOutputFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt)85
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/OutputFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt)10
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/PackageFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SanitizationFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SubclassFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt)75
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassPredicate.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassPredicate.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt243
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/Trie.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt)52
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/Helper.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt)2
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt218
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt30
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt414
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt60
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt44
-rw-r--r--ravenwood/tools/ravenhelper/Android.bp6
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt90
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt6
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt84
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt2
-rw-r--r--ravenwood/tools/ravenizer/Android.bp11
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt110
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt30
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt125
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt4
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt6
-rw-r--r--services/accessibility/accessibility.aconfig20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java56
-rw-r--r--services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java52
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java119
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java193
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java74
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java23
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java56
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java66
-rw-r--r--services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java100
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java42
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java31
-rw-r--r--services/backup/java/com/android/server/backup/BackupRestoreTask.java31
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java47
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java7
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java2
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java53
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java5
-rw-r--r--services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java21
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java10
-rw-r--r--services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java2
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java5
-rw-r--r--services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java2
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java14
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java10
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/SecureChannel.java37
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java35
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java10
-rw-r--r--services/core/Android.bp9
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java2
-rw-r--r--services/core/java/com/android/server/Watchdog.java1
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java6
-rw-r--r--services/core/java/com/android/server/adb/AdbDebuggingManager.java48
-rw-r--r--services/core/java/com/android/server/adb/AdbService.java114
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java18
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java18
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java72
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java142
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java63
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java61
-rw-r--r--services/core/java/com/android/server/am/ConnectionRecord.java7
-rw-r--r--services/core/java/com/android/server/am/OWNERS2
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java23
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java9
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java32
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java9
-rw-r--r--services/core/java/com/android/server/am/UserController.java56
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java27
-rw-r--r--services/core/java/com/android/server/am/broadcasts_flags.aconfig13
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java27
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java13
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java24
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java11
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsRegistry.java154
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java67
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java8
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java11
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java76
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistryInterface.java153
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistrySql.java145
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java181
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java33
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java90
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java2
-rw-r--r--services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java24
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java5
-rw-r--r--services/core/java/com/android/server/display/DisplayAdapter.java16
-rw-r--r--services/core/java/com/android/server/display/DisplayControl.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayGroup.java12
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java41
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java21
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java32
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java6
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java15
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java37
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java92
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayAdapter.java6
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessReason.java9
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java53
-rw-r--r--services/core/java/com/android/server/display/mode/ModeChangeObserver.java128
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java11
-rw-r--r--services/core/java/com/android/server/infra/AbstractMasterSystemService.java389
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java5
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java24
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java109
-rw-r--r--services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java24
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java18
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java44
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java27
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java8
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java153
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssManagerService.java41
-rw-r--r--services/core/java/com/android/server/location/gnss/hal/GnssNative.java46
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java6
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java8
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java408
-rw-r--r--services/core/java/com/android/server/media/MediaRouterMetricLogger.java219
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java33
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java117
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityUtils.java7
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java13
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java56
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java17
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java26
-rw-r--r--services/core/java/com/android/server/notification/PermissionHelper.java21
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java9
-rw-r--r--services/core/java/com/android/server/notification/TEST_MAPPING5
-rw-r--r--services/core/java/com/android/server/notification/ZenConfigTrimmer.java109
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java15
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java4
-rw-r--r--services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java13
-rw-r--r--services/core/java/com/android/server/os/instrumentation/OWNERS1
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java14
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java44
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java14
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java2
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING64
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java12
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java31
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java89
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java14
-rw-r--r--services/core/java/com/android/server/power/Notifier.java52
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java11
-rw-r--r--services/core/java/com/android/server/power/ScreenUndimDetector.java44
-rw-r--r--services/core/java/com/android/server/power/WakeLockLog.java42
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java42
-rw-r--r--services/core/java/com/android/server/security/CertificateRevocationStatusManager.java5
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/UsbDataAdvancedProtectionHook.java142
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java22
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java109
-rw-r--r--services/core/java/com/android/server/selinux/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/storage/StorageUserConnection.java40
-rw-r--r--services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java61
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java124
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java141
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationThread.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorDebugUtils.java37
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java4
-rw-r--r--services/core/java/com/android/server/vr/EnabledComponentsObserver.java62
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java91
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java8
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java207
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java102
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java31
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java45
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java306
-rw-r--r--services/core/java/com/android/server/wm/ActivityRefresher.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java29
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java40
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java116
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java42
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java18
-rw-r--r--services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java39
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java14
-rw-r--r--services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java7
-rw-r--r--services/core/java/com/android/server/wm/AppCompatRoundedCorners.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java167
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java8
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java1587
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java44
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java18
-rw-r--r--services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java84
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java8
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java28
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java57
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java10
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java82
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java176
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java64
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java87
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowListenerController.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java9
-rw-r--r--services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java31
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java63
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java7
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java1
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java22
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java7
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java13
-rw-r--r--services/core/java/com/android/server/wm/LaunchParamsController.java50
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java23
-rw-r--r--services/core/java/com/android/server/wm/PinnedTaskController.java102
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java65
-rw-r--r--services/core/java/com/android/server/wm/Task.java48
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java56
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java373
-rw-r--r--services/core/java/com/android/server/wm/Transition.java5
-rw-r--r--services/core/java/com/android/server/wm/ViewServer.java9
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java67
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java200
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java53
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java31
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java172
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java86
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessControllerMap.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java160
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java3
-rw-r--r--services/core/jni/Android.bp7
-rw-r--r--services/core/jni/com_android_server_display_DisplayControl.cpp7
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp15
-rw-r--r--services/core/jni/com_android_server_location_GnssLocationProvider.cpp36
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorController.cpp28
-rw-r--r--services/core/jni/gnss/Android.bp5
-rw-r--r--services/core/jni/gnss/Gnss.cpp11
-rw-r--r--services/core/jni/gnss/Gnss.h2
-rw-r--r--services/core/jni/gnss/GnssAssistance.cpp2047
-rw-r--r--services/core/jni/gnss/GnssAssistance.h135
-rw-r--r--services/core/jni/gnss/GnssAssistanceCallback.cpp48
-rw-r--r--services/core/jni/gnss/GnssAssistanceCallback.h48
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java73
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java7
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/java/com/android/server/flags.aconfig8
-rw-r--r--services/proguard.flags1
-rw-r--r--services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java9
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/Android.bp1
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/OWNERS1
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java3
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java22
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp5
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml5
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java10
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java3
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java3
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java3
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml3
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java4
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java27
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt36
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java98
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java119
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java8
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt146
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java81
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java6
-rw-r--r--services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java1
-rw-r--r--services/tests/media/mediarouterservicetest/Android.bp10
-rw-r--r--services/tests/media/mediarouterservicetest/AndroidTest.xml3
-rw-r--r--services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java140
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java43
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java55
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java141
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java50
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java38
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java156
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java378
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java (renamed from services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java)0
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java181
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java340
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java78
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java85
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java72
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java2
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp1
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java50
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/PinnerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java165
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java239
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java167
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java92
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java77
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java94
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java79
-rw-r--r--services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java86
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java57
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java91
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java47
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java8
-rw-r--r--services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java8
-rw-r--r--services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java10
-rw-r--r--services/tests/uiservicestests/Android.bp83
-rw-r--r--services/tests/uiservicestests/AndroidTest.xml1
-rw-r--r--services/tests/uiservicestests/notification-tests.xml30
-rw-r--r--services/tests/uiservicestests/notification-zen-tests.xml30
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java14
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java11
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java139
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java43
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java59
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java92
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java124
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java44
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java47
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java9
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java67
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java102
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java873
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java121
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DimmerTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java71
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java125
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java33
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java189
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java245
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java152
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java49
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java4
-rw-r--r--services/usb/java/com/android/server/usb/UsbManagerInternal.java50
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java48
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java22
-rw-r--r--telecomm/java/android/telecom/Call.java28
-rw-r--r--telecomm/java/android/telecom/Connection.java4
-rw-r--r--telecomm/java/android/telecom/ParcelableCall.java32
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java2
-rw-r--r--telephony/java/android/telephony/PreciseDataConnectionState.java9
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java55
-rw-r--r--telephony/java/android/telephony/TelephonyFrameworkInitializer.java4
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java15
-rw-r--r--telephony/java/android/telephony/data/DataCallResponse.java4
-rw-r--r--telephony/java/android/telephony/data/DataService.java3
-rw-r--r--telephony/java/android/telephony/data/QualifiedNetworksService.java12
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl25
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java8
-rw-r--r--tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/AppClose/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/FlickerService/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/IME/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/Notification/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/Rotation/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt14
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt68
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml27
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml3
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml3
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml3
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml2
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java2
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java8
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java8
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java2
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java5
-rw-r--r--tests/Input/assets/testPointerScale.pngbin949 -> 947 bytes
-rw-r--r--tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt4
-rw-r--r--tests/Input/src/com/android/server/input/BatteryControllerTests.kt4
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt118
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java8
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java10
-rw-r--r--tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java6
-rw-r--r--tests/PackageWatchdog/Android.bp4
-rw-r--r--tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java523
-rw-r--r--tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt4
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java77
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java41
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperTest.java4
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java11
-rw-r--r--tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java2
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java8
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java2
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java4
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java6
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnTest.java8
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java5
-rw-r--r--tools/aapt2/Resource.h2
-rw-r--r--tools/aapt2/ResourceTable.cpp9
-rw-r--r--tools/aapt2/ResourceTable.h5
-rw-r--r--tools/aapt2/cmd/Link.cpp12
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp2
-rw-r--r--tools/aapt2/format/binary/ResEntryWriter.cpp4
-rw-r--r--tools/aapt2/format/binary/ResEntryWriter.h2
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp3
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp19
-rw-r--r--tools/aapt2/link/FlaggedResources_test.cpp48
-rw-r--r--tools/aapt2/link/FlaggedXmlVersioner.cpp27
-rw-r--r--tools/codegen/src/com/android/codegen/Debug.kt2
-rw-r--r--tools/codegen/src/com/android/codegen/FeatureFlag.kt4
-rw-r--r--tools/codegen/src/com/android/codegen/FileInfo.kt8
-rw-r--r--tools/codegen/src/com/android/codegen/Generators.kt4
-rw-r--r--tools/codegen/src/com/android/codegen/Utils.kt2
-rw-r--r--tools/fonts/Android.bp5
-rw-r--r--tools/lint/fix/Android.bp5
-rw-r--r--tools/lint/global/integration_tests/Android.bp5
-rw-r--r--tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt18
-rw-r--r--wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java2
2794 files changed, 88859 insertions, 31648 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 8bfac03060b5..12781e404ba9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -73,6 +73,7 @@ aconfig_declarations_group {
"android.service.dreams.flags-aconfig-java",
"android.service.notification.flags-aconfig-java",
"android.service.quickaccesswallet.flags-aconfig-java",
+ "android.service.selinux.flags-aconfig-java",
"android.service.voice.flags-aconfig-java",
"android.speech.flags-aconfig-java",
"android.systemserver.flags-aconfig-java",
@@ -110,6 +111,7 @@ aconfig_declarations_group {
"com.android.window.flags.window-aconfig-java",
"configinfra_framework_flags_java_exported_lib",
"conscrypt_exported_aconfig_flags_lib",
+ "sdk_sandbox_exported_flags_lib",
"device_policy_aconfig_flags_lib",
"display_flags_lib",
"dropbox_flags_lib",
@@ -123,7 +125,6 @@ aconfig_declarations_group {
"libcore_readonly_aconfig_flags_lib",
"libgui_flags_java_lib",
"power_flags_lib",
- "sdk_sandbox_flags_lib",
"surfaceflinger_flags_java_lib",
"telecom_flags_core_java_lib",
"telephony_flags_core_java_lib",
@@ -264,6 +265,7 @@ java_aconfig_library {
cc_aconfig_library {
name: "com.android.window.flags.window-aconfig_flags_c_lib",
aconfig_declarations: "com.android.window.flags.window-aconfig",
+ host_supported: true,
}
// DeviceStateManager
@@ -1943,3 +1945,19 @@ java_aconfig_library {
aconfig_declarations: "android.service.quickaccesswallet.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// SELinux log collector
+aconfig_declarations {
+ name: "android.service.selinux.flags-aconfig",
+ package: "com.android.server.selinux.flags",
+ container: "system",
+ srcs: [
+ "services/core/java/com/android/server/selinux/*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.service.selinux.flags-aconfig-java",
+ aconfig_declarations: "android.service.selinux.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 303fa2cd18da..127556f8e075 100644
--- a/Android.bp
+++ b/Android.bp
@@ -103,10 +103,10 @@ filegroup {
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
- ":android.hardware.radio-V4-java-source",
- ":android.hardware.radio.data-V4-java-source",
- ":android.hardware.radio.network-V4-java-source",
- ":android.hardware.radio.voice-V4-java-source",
+ ":android.hardware.radio-V5-java-source",
+ ":android.hardware.radio.data-V5-java-source",
+ ":android.hardware.radio.network-V5-java-source",
+ ":android.hardware.radio.voice-V5-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.thermal-V3-java-source",
":android.hardware.tv.tuner-V3-java-source",
@@ -232,13 +232,13 @@ java_library {
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.radio-V1.6-java",
- "android.hardware.radio.data-V4-java",
- "android.hardware.radio.ims-V3-java",
- "android.hardware.radio.messaging-V4-java",
- "android.hardware.radio.modem-V4-java",
- "android.hardware.radio.network-V4-java",
- "android.hardware.radio.sim-V4-java",
- "android.hardware.radio.voice-V4-java",
+ "android.hardware.radio.data-V5-java",
+ "android.hardware.radio.ims-V4-java",
+ "android.hardware.radio.messaging-V5-java",
+ "android.hardware.radio.modem-V5-java",
+ "android.hardware.radio.network-V5-java",
+ "android.hardware.radio.sim-V5-java",
+ "android.hardware.radio.voice-V5-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
@@ -415,6 +415,7 @@ java_defaults {
"mimemap",
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
+ "media_quality_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
"modules-utils-binary-xml",
"modules-utils-build",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 05d6e886b01a..e862cd9e0a95 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,6 +6,7 @@ ktfmt = true
[Builtin Hooks Options]
# Only turn on clang-format check for the following subfolders.
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ apct-tests/
cmds/hid/
cmds/input/
cmds/uinput/
@@ -18,7 +19,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
tests/
tools/
bpfmt = -d
-ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode,libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode
+ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode,libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode,apct-tests
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 2e038e00bb35..a16b4406f217 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -19,6 +19,7 @@ java_library {
name: "framework-minus-apex-for-host",
installable: false,
static_libs: ["framework-minus-apex"],
+ srcs: [":framework-ravenwood-sources"],
visibility: ["//frameworks/base/ravenwood"],
}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e469f167d32f..ce0da7e21071 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -25,6 +25,12 @@
"name": "FrameworksUiServicesTests"
},
{
+ "name": "FrameworksUiServicesNotificationTests"
+ },
+ {
+ "name": "FrameworksUiServicesZenTests"
+ },
+ {
"name": "FrameworksInputMethodSystemServerTests_server_inputmethod"
},
{
diff --git a/apct-tests/perftests/core/src/android/input/OWNERS b/apct-tests/perftests/core/src/android/input/OWNERS
deleted file mode 100644
index 95e3f0213f49..000000000000
--- a/apct-tests/perftests/core/src/android/input/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-include platform/frameworks/base:/INPUT_OWNERS
-
-# Bug component: 136048
diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
index 7a7250b9e910..8e3ed6d9931c 100644
--- a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
@@ -19,24 +19,27 @@ package android.view;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
-@SmallTest
+@LargeTest
+@RunWith(AndroidJUnit4.class)
public class ViewConfigurationPerfTest {
@Rule
- public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private final Context mContext = getInstrumentation().getTargetContext();
@Test
public void testGet_newViewConfiguration() {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
@@ -50,7 +53,7 @@ public class ViewConfigurationPerfTest {
@Test
public void testGet_cachedViewConfiguration() {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
// Do `get` once to make sure there's something cached.
ViewConfiguration.get(mContext);
@@ -58,4 +61,265 @@ public class ViewConfigurationPerfTest {
ViewConfiguration.get(mContext);
}
}
+
+ @Test
+ public void testGetPressedStateDuration_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getPressedStateDuration();
+ }
+ }
+
+ @Test
+ public void testGetPressedStateDuration_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getPressedStateDuration();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getPressedStateDuration();
+ }
+ }
+
+ @Test
+ public void testGetTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetJumpTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getJumpTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetJumpTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getJumpTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getJumpTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDoubleTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDoubleTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDoubleTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapMinTime_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDoubleTapMinTime();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapMinTime_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDoubleTapMinTime();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDoubleTapMinTime();
+ }
+ }
+
+ @Test
+ public void testGetZoomControlsTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getZoomControlsTimeout();
+ }
+ }
+
+ @Test
+ public void testGetZoomControlsTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getZoomControlsTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getZoomControlsTimeout();
+ }
+ }
+
+ @Test
+ public void testGetLongPressTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getLongPressTimeout();
+ }
+ }
+
+ @Test
+ public void testGetMultiPressTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getMultiPressTimeout();
+ }
+ }
+
+ @Test
+ public void testGetKeyRepeatTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getKeyRepeatTimeout();
+ }
+ }
+
+ @Test
+ public void testGetKeyRepeatDelay() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getKeyRepeatDelay();
+ }
+ }
+
+ @Test
+ public void testGetHoverTapSlop_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getHoverTapSlop();
+ }
+ }
+
+ @Test
+ public void testGetHoverTapSlop_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getHoverTapSlop();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getHoverTapSlop();
+ }
+ }
+
+ @Test
+ public void testGetScrollFriction_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getScrollFriction();
+ }
+ }
+
+ @Test
+ public void testGetScrollFriction_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getScrollFriction();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getScrollFriction();
+ }
+ }
+
+ @Test
+ public void testGetDefaultActionModeHideDuration_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDefaultActionModeHideDuration();
+ }
+ }
+
+ @Test
+ public void testGetDefaultActionModeHideDuration_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDefaultActionModeHideDuration();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDefaultActionModeHideDuration();
+ }
+ }
}
diff --git a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
index 67b33e5d157b..d2321ec7934f 100644
--- a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
@@ -84,12 +84,10 @@ public class ViewPerfTest {
final BenchmarkState state = mBenchmarkRule.getState();
mActivityRule.runOnUiThread(() -> {
- state.pauseTiming();
View view = new View(mContext);
mActivityRule.getActivity().setContentView(view);
assertTrue("View needs to be attached to Window to perform haptic feedback",
view.isAttachedToWindow());
- state.resumeTiming();
// Disable settings so perform will never be ignored.
int flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
index 21a4ca048f62..bdb54c9dc0ac 100644
--- a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
+++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
@@ -32,8 +32,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class HealthConnectReadWritePerfTest {
- @get:Rule
- val perfStatusReporter = PerfStatusReporter()
+ @get:Rule val perfStatusReporter = PerfStatusReporter()
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
@@ -41,9 +40,7 @@ class HealthConnectReadWritePerfTest {
requireNotNull(context.getSystemService(HealthConnectManager::class.java))
}
- /**
- * A first empty test just to setup the test package and make sure it runs properly.
- */
+ /** A first empty test just to setup the test package and make sure it runs properly. */
@Test
fun placeholder() {
val state = perfStatusReporter.benchmarkState
@@ -51,4 +48,4 @@ class HealthConnectReadWritePerfTest {
SystemClock.sleep(100)
}
}
-} \ No newline at end of file
+}
diff --git a/apct-tests/perftests/input/Android.bp b/apct-tests/perftests/input/Android.bp
new file mode 100644
index 000000000000..21b66cf3245c
--- /dev/null
+++ b/apct-tests/perftests/input/Android.bp
@@ -0,0 +1,44 @@
+// Copyright 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES 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_team: "trendy_team_input_framework",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "InputPerfTests",
+ srcs: ["src/**/*.kt"],
+ kotlincflags: [
+ "-Werror",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ "apct-perftests-utils",
+ "collector-device-lib",
+ "compatibility-device-util-axt",
+ "cts-input-lib",
+ "platform-test-annotations",
+ ],
+ test_suites: ["device-tests"],
+ data: [":perfetto_artifacts"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/apct-tests/perftests/input/AndroidManifest.xml b/apct-tests/perftests/input/AndroidManifest.xml
new file mode 100644
index 000000000000..e9e4fd071b47
--- /dev/null
+++ b/apct-tests/perftests/input/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.input">
+
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.cts.input.CaptureEventActivity"
+ android:label="Capture events"
+ android:configChanges="touchscreen|uiMode|orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
+ android:enableOnBackInvokedCallback="false"
+ android:turnScreenOn="true"
+ android:exported="true">
+ </activity>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.input">
+ <meta-data android:name="listener" android:value="android.input.InputPerfRunPrecondition" />
+ </instrumentation>
+</manifest>
diff --git a/apct-tests/perftests/input/OWNERS b/apct-tests/perftests/input/OWNERS
new file mode 100644
index 000000000000..3cffce960b1c
--- /dev/null
+++ b/apct-tests/perftests/input/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 136048
+include /core/java/android/hardware/input/OWNERS
diff --git a/apct-tests/perftests/input/src/android/input/InputPerfRunPrecondition.kt b/apct-tests/perftests/input/src/android/input/InputPerfRunPrecondition.kt
new file mode 100644
index 000000000000..d992380241a4
--- /dev/null
+++ b/apct-tests/perftests/input/src/android/input/InputPerfRunPrecondition.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.input
+
+import android.perftests.utils.WindowPerfRunPreconditionBase
+
+/** Prepare the preconditions before running performance test. */
+class InputPerfRunPrecondition : WindowPerfRunPreconditionBase()
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/input/src/android/input/MotionPredictorBenchmark.kt
index add0a086201b..cf93331d87c2 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/input/src/android/input/MotionPredictorBenchmark.kt
@@ -25,11 +25,10 @@ import android.view.MotionEvent.ACTION_MOVE
import android.view.MotionEvent.PointerCoords
import android.view.MotionEvent.PointerProperties
import android.view.MotionPredictor
-
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.filters.LargeTest
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import java.time.Duration
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -37,14 +36,12 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.time.Duration
-
private fun getStylusMotionEvent(
- eventTime: Duration,
- action: Int,
- x: Float,
- y: Float,
- ): MotionEvent{
+ eventTime: Duration,
+ action: Int,
+ x: Float,
+ y: Float,
+): MotionEvent {
val pointerCount = 1
val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
@@ -58,37 +55,49 @@ private fun getStylusMotionEvent(
coords[i]!!.y = y
}
- return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size,
- properties, coords, /*metaState=*/0, /*buttonState=*/0,
- /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0,
- InputDevice.SOURCE_STYLUS, /*flags=*/0)
+ return MotionEvent.obtain(
+ /*downTime=*/ 0,
+ eventTime.toMillis(),
+ action,
+ properties.size,
+ properties,
+ coords,
+ /*metaState=*/ 0,
+ /*buttonState=*/ 0,
+ /*xPrecision=*/ 0f,
+ /*yPrecision=*/ 0f,
+ /*deviceId=*/ 0,
+ /*edgeFlags=*/ 0,
+ InputDevice.SOURCE_STYLUS,
+ /*flags=*/ 0,
+ )
}
@RunWith(AndroidJUnit4::class)
@LargeTest
class MotionPredictorBenchmark {
private val instrumentation = InstrumentationRegistry.getInstrumentation()
- @get:Rule
- val perfStatusReporter = PerfStatusReporter()
+ @get:Rule val perfStatusReporter = PerfStatusReporter()
private val initialPropertyValue =
- SystemProperties.get("persist.input.enable_motion_prediction")
-
+ SystemProperties.get("persist.input.enable_motion_prediction")
@Before
fun setUp() {
instrumentation.uiAutomation.executeShellCommand(
- "setprop persist.input.enable_motion_prediction true")
+ "setprop persist.input.enable_motion_prediction true"
+ )
}
@After
fun tearDown() {
instrumentation.uiAutomation.executeShellCommand(
- "setprop persist.input.enable_motion_prediction $initialPropertyValue")
+ "setprop persist.input.enable_motion_prediction $initialPropertyValue"
+ )
}
/**
- * In a typical usage, app will send the event to the predictor and then call .predict to draw
- * a prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this.
+ * In a typical usage, app will send the event to the predictor and then call .predict to draw a
+ * prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this.
*/
@Test
fun timeRecordAndPredict() {
@@ -99,10 +108,16 @@ class MotionPredictorBenchmark {
var eventPosition = 0f
val positionInterval = 10f
- val predictor = MotionPredictor(/*isPredictionEnabled=*/true, offset.toNanos().toInt())
+ val predictor = MotionPredictor(/* isPredictionEnabled= */ true, offset.toNanos().toInt())
// ACTION_DOWN t=0 x=0 y=0
- predictor.record(getStylusMotionEvent(
- eventTime, ACTION_DOWN, /*x=*/eventPosition, /*y=*/eventPosition))
+ predictor.record(
+ getStylusMotionEvent(
+ eventTime,
+ ACTION_DOWN,
+ /*x=*/ eventPosition,
+ /*y=*/ eventPosition,
+ )
+ )
val state = perfStatusReporter.getBenchmarkState()
while (state.keepRunning()) {
@@ -110,8 +125,13 @@ class MotionPredictorBenchmark {
eventPosition += positionInterval
// Send MOVE event and then call .predict
- val moveEvent = getStylusMotionEvent(
- eventTime, ACTION_MOVE, /*x=*/eventPosition, /*y=*/eventPosition)
+ val moveEvent =
+ getStylusMotionEvent(
+ eventTime,
+ ACTION_MOVE,
+ /*x=*/ eventPosition,
+ /*y=*/ eventPosition,
+ )
predictor.record(moveEvent)
val predictionTime = eventTime + eventInterval
val predicted = checkNotNull(predictor.predict(predictionTime.toNanos()))
@@ -129,7 +149,7 @@ class MotionPredictorBenchmark {
val state = perfStatusReporter.getBenchmarkState()
while (state.keepRunning()) {
- MotionPredictor(/*isPredictionEnabled=*/true, offsetNanos)
+ MotionPredictor(/* isPredictionEnabled= */ true, offsetNanos)
}
}
}
diff --git a/apct-tests/perftests/input/src/android/input/TouchPerfTest.kt b/apct-tests/perftests/input/src/android/input/TouchPerfTest.kt
new file mode 100644
index 000000000000..26f101d91573
--- /dev/null
+++ b/apct-tests/perftests/input/src/android/input/TouchPerfTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.input
+
+import android.cts.input.EventVerifier
+import android.perftests.utils.PerfStatusReporter
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.input.CaptureEventActivity
+import com.android.cts.input.UinputTouchScreen
+import com.android.cts.input.VirtualDisplayActivityScenario
+import com.android.cts.input.inputeventmatchers.withMotionAction
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TouchPerfTest {
+ @get:Rule val testName = TestName()
+ @get:Rule val perfStatusReporter = PerfStatusReporter()
+ @get:Rule
+ val virtualDisplayRule = VirtualDisplayActivityScenario.Rule<CaptureEventActivity>(testName)
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @Test
+ fun testDownMoveUp() {
+ UinputTouchScreen(instrumentation, virtualDisplayRule.virtualDisplay.display).use {
+ touchScreen ->
+ val verifier = EventVerifier(virtualDisplayRule.activity::getInputEvent)
+ val state = perfStatusReporter.benchmarkState
+
+ while (state.keepRunning()) {
+ val x = 100
+ val y = 100
+
+ val pointer = touchScreen.touchDown(x, y)
+ verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN))
+
+ pointer.moveTo(x + 1, y + 1)
+ verifier.assertReceivedMotion(withMotionAction(ACTION_MOVE))
+
+ pointer.lift()
+ verifier.assertReceivedMotion(withMotionAction(ACTION_UP))
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt b/apct-tests/perftests/input/src/android/input/VelocityTrackerBenchmarkTest.kt
index 530ca7b7e5a0..df58cca42d44 100644
--- a/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
+++ b/apct-tests/perftests/input/src/android/input/VelocityTrackerBenchmarkTest.kt
@@ -19,12 +19,9 @@ import android.perftests.utils.PerfStatusReporter
import android.view.InputDevice
import android.view.MotionEvent
import android.view.VelocityTracker
-
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.runner.AndroidJUnit4
-
import java.time.Duration
-
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
@@ -74,22 +71,23 @@ private class ScrollMotionState : MotionState() {
props.id = 0
val coords = MotionEvent.PointerCoords()
coords.setAxisValue(MotionEvent.AXIS_SCROLL, DEFAULT_SCROLL_AMOUNT)
- val motionEvent = MotionEvent.obtain(
- /*downTime=*/0,
- currentTime.toMillis(),
- MotionEvent.ACTION_SCROLL,
- /*pointerCount=*/1,
- arrayOf(props),
- arrayOf(coords),
- /*metaState=*/0,
- /*buttonState=*/0,
- /*xPrecision=*/0f,
- /*yPrecision=*/0f,
- /*deviceId=*/1,
- /*edgeFlags=*/0,
- InputDevice.SOURCE_ROTARY_ENCODER,
- /*flags=*/0
- )
+ val motionEvent =
+ MotionEvent.obtain(
+ /*downTime=*/ 0,
+ currentTime.toMillis(),
+ MotionEvent.ACTION_SCROLL,
+ /*pointerCount=*/ 1,
+ arrayOf(props),
+ arrayOf(coords),
+ /*metaState=*/ 0,
+ /*buttonState=*/ 0,
+ /*xPrecision=*/ 0f,
+ /*yPrecision=*/ 0f,
+ /*deviceId=*/ 1,
+ /*edgeFlags=*/ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ /*flags=*/ 0,
+ )
currentTime = currentTime.plus(DEFAULT_TIME_JUMP)
@@ -113,13 +111,15 @@ private class PlanarMotionState : MotionState() {
override fun createMotionEvent(): MotionEvent {
val action: Int = if (downEventCreated) MotionEvent.ACTION_MOVE else MotionEvent.ACTION_DOWN
- val motionEvent = MotionEvent.obtain(
- /*downTime=*/START_TIME.toMillis(),
- currentTime.toMillis(),
- action,
- x,
- y,
- /*metaState=*/0)
+ val motionEvent =
+ MotionEvent.obtain(
+ /*downTime=*/ START_TIME.toMillis(),
+ currentTime.toMillis(),
+ action,
+ x,
+ y,
+ /*metaState=*/ 0,
+ )
if (downEventCreated) {
x += INCREMENT
@@ -155,16 +155,15 @@ private class PlanarMotionState : MotionState() {
/**
* Benchmark tests for [VelocityTracker]
*
- * Build/Install/Run:
- * atest VelocityTrackerBenchmarkTest
+ * Build/Install/Run: atest VelocityTrackerBenchmarkTest
*/
@LargeTest
@RunWith(AndroidJUnit4::class)
class VelocityTrackerBenchmarkTest {
- @get:Rule
- val perfStatusReporter: PerfStatusReporter = PerfStatusReporter()
+ @get:Rule val perfStatusReporter: PerfStatusReporter = PerfStatusReporter()
private val velocityTracker = VelocityTracker.obtain()
+
@Before
fun setup() {
velocityTracker.clear()
@@ -255,4 +254,4 @@ class VelocityTrackerBenchmarkTest {
companion object {
private const val TEST_NUM_DATAPOINTS = 100
}
-} \ No newline at end of file
+}
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index ea10690bd672..48f2c1554c30 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -51,51 +51,52 @@ public class PackageParsingPerfTest {
private const val QUEUE_POLL_TIMEOUT_SECONDS = 5L
// TODO: Replace this with core version of SYSTEM_PARTITIONS
- val FOLDERS_TO_TEST = listOf(
- Environment.getRootDirectory(),
- Environment.getVendorDirectory(),
- Environment.getOdmDirectory(),
- Environment.getOemDirectory(),
- Environment.getOemDirectory(),
- Environment.getSystemExtDirectory()
- )
+ val FOLDERS_TO_TEST =
+ listOf(
+ Environment.getRootDirectory(),
+ Environment.getVendorDirectory(),
+ Environment.getOdmDirectory(),
+ Environment.getOemDirectory(),
+ Environment.getOemDirectory(),
+ Environment.getSystemExtDirectory(),
+ )
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun parameters(): Array<Params> {
- val apks = FOLDERS_TO_TEST
- .filter(File::exists)
- .map(File::walkTopDown)
- .flatMap(Sequence<File>::asIterable)
- .filter { it.name.endsWith(".apk") }
+ val apks =
+ FOLDERS_TO_TEST.filter(File::exists)
+ .map(File::walkTopDown)
+ .flatMap(Sequence<File>::asIterable)
+ .filter { it.name.endsWith(".apk") }
return arrayOf(
Params(1, apks) { ParallelParser1(it?.let(::PackageCacher1)) },
- Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) }
+ Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) },
)
}
data class Params(
val version: Int,
val apks: List<File>,
- val cacheDirToParser: (File?) -> ParallelParser<*>
+ val cacheDirToParser: (File?) -> ParallelParser<*>,
) {
// For test name formatting
override fun toString() = "v$version"
}
}
- @get:Rule
- var perfStatusReporter = PerfStatusReporter()
+ @get:Rule var perfStatusReporter = PerfStatusReporter()
+
+ @get:Rule var testFolder = TemporaryFolder()
- @get:Rule
- var testFolder = TemporaryFolder()
+ @Parameterized.Parameter(0) lateinit var params: Params
- @Parameterized.Parameter(0)
- lateinit var params: Params
+ private val state: BenchmarkState
+ get() = perfStatusReporter.benchmarkState
- private val state: BenchmarkState get() = perfStatusReporter.benchmarkState
- private val apks: List<File> get() = params.apks
+ private val apks: List<File>
+ get() = params.apks
private fun safeParse(parser: ParallelParser<*>, file: File) {
try {
@@ -109,9 +110,7 @@ public class PackageParsingPerfTest {
fun sequentialNoCache() {
params.cacheDirToParser(null).use { parser ->
while (state.keepRunning()) {
- apks.forEach {
- safeParse(parser, it)
- }
+ apks.forEach { safeParse(parser, it) }
}
}
}
@@ -155,18 +154,21 @@ public class PackageParsingPerfTest {
private val cacher: PackageCacher<PackageType>? = null
) : AutoCloseable {
private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY)
- private val service = ConcurrentUtils.newFixedThreadPool(
- PARALLEL_MAX_THREADS, "package-parsing-test",
- Process.THREAD_PRIORITY_FOREGROUND)
+ private val service =
+ ConcurrentUtils.newFixedThreadPool(
+ PARALLEL_MAX_THREADS,
+ "package-parsing-test",
+ Process.THREAD_PRIORITY_FOREGROUND,
+ )
fun submit(file: File) {
- service.submit {
- try {
- queue.put(parse(file))
- } catch (e: Exception) {
- queue.put(e)
- }
+ service.submit {
+ try {
+ queue.put(parse(file))
+ } catch (e: Exception) {
+ queue.put(e)
}
+ }
}
fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
@@ -175,57 +177,51 @@ public class PackageParsingPerfTest {
service.shutdownNow()
}
- fun parse(file: File) = cacher?.getCachedResult(file)
- ?: parseImpl(file).also { cacher?.cacheResult(file, it) }
+ fun parse(file: File) =
+ cacher?.getCachedResult(file) ?: parseImpl(file).also { cacher?.cacheResult(file, it) }
protected abstract fun parseImpl(file: File): PackageType
}
class ParallelParser1(private val cacher: PackageCacher1? = null) :
ParallelParser<PackageParser.Package>(cacher) {
- val parser = PackageParser().apply {
- setCallback { true }
- }
+ val parser = PackageParser().apply { setCallback { true } }
override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
}
- class ParallelParser2(cacher: PackageCacher2? = null) :
- ParallelParser<PackageImpl>(cacher) {
- val input = ThreadLocal.withInitial {
- // For testing, just disable enforcement to avoid hooking up to compat framework
- ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
- }
- val parser = ParsingPackageUtils(null,
- null,
- emptyList(),
- object :
- ParsingPackageUtils.Callback {
- override fun hasFeature(feature: String) = true
-
- override fun startParsingPackage(
- packageName: String,
- baseApkPath: String,
- path: String,
- manifestArray: TypedArray,
- isCoreApp: Boolean
- ) = PackageImpl(
- packageName,
- baseApkPath,
- path,
- manifestArray,
- isCoreApp,
- this,
- )
- override fun getHiddenApiWhitelistedApps() =
+ class ParallelParser2(cacher: PackageCacher2? = null) : ParallelParser<PackageImpl>(cacher) {
+ val input =
+ ThreadLocal.withInitial {
+ // For testing, just disable enforcement to avoid hooking up to compat framework
+ ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
+ }
+ val parser =
+ ParsingPackageUtils(
+ null,
+ null,
+ emptyList(),
+ object : ParsingPackageUtils.Callback {
+ override fun hasFeature(feature: String) = true
+
+ override fun startParsingPackage(
+ packageName: String,
+ baseApkPath: String,
+ path: String,
+ manifestArray: TypedArray,
+ isCoreApp: Boolean,
+ ) = PackageImpl(packageName, baseApkPath, path, manifestArray, isCoreApp, this)
+
+ override fun getHiddenApiWhitelistedApps() =
SystemConfig.getInstance().hiddenApiWhitelistedApps
- override fun getInstallConstraintsAllowlist() =
+
+ override fun getInstallConstraintsAllowlist() =
SystemConfig.getInstance().installConstraintsAllowlist
- })
+ },
+ )
override fun parseImpl(file: File) =
- parser.parsePackage(input.get()!!.reset(), file, 0).result
- as PackageImpl
+ parser.parsePackage(input.get()!!.reset(), file, 0).result as PackageImpl
}
abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
@@ -237,14 +233,13 @@ public class PackageParsingPerfTest {
}
val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
- val parcel = Parcel.obtain().apply {
- unmarshall(bytes, 0, bytes.size)
- setDataPosition(0)
- }
+ val parcel =
+ Parcel.obtain().apply {
+ unmarshall(bytes, 0, bytes.size)
+ setDataPosition(0)
+ }
ReadHelper(parcel).apply { startAndInstall() }
- return fromParcel(parcel).also {
- parcel.recycle()
- }
+ return fromParcel(parcel).also { parcel.recycle() }
}
fun cacheResult(file: File, parsed: Parcelable) {
@@ -263,26 +258,19 @@ public class PackageParsingPerfTest {
val helper = WriteHelper(parcel)
pkg.writeToParcel(parcel, 0 /* flags */)
helper.finishAndUninstall()
- return parcel.marshall().also {
- parcel.recycle()
- }
+ return parcel.marshall().also { parcel.recycle() }
}
protected abstract fun fromParcel(parcel: Parcel): PackageType
}
- /**
- * Re-implementation of v1's cache, since that's gone in R+.
- */
+ /** Re-implementation of v1's cache, since that's gone in R+. */
class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
}
- /**
- * Re-implementation of the server side PackageCacher, as it's inaccessible here.
- */
+ /** Re-implementation of the server side PackageCacher, as it's inaccessible here. */
class PackageCacher2(cacheDir: File) : PackageCacher<PackageImpl>(cacheDir) {
- override fun fromParcel(parcel: Parcel) =
- PackageImpl(parcel)
+ override fun fromParcel(parcel: Parcel) = PackageImpl(parcel)
}
}
diff --git a/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt
index b6a53bf8226a..7d4061a10b37 100644
--- a/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt
+++ b/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt
@@ -50,7 +50,7 @@ class AppOpsPerfTest {
opPackageUid,
opPackageName,
null,
- null
+ null,
)
}
}
@@ -62,7 +62,7 @@ class AppOpsPerfTest {
appOpsManager.unsafeCheckOp(
AppOpsManager.OPSTR_FINE_LOCATION,
opPackageUid,
- opPackageName
+ opPackageName,
)
}
}
diff --git a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
index 1139835d3e7b..b64d5d92c46e 100644
--- a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
+++ b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
@@ -28,24 +28,26 @@ import com.android.compatibility.common.util.AdoptShellPermissionsRule
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.util.concurrent.TimeUnit
import java.util.function.BiConsumer
import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PermissionServicePerfTest {
@get:Rule val mPerfManualStatusReporter = PerfManualStatusReporter()
- @get:Rule val mAdoptShellPermissionsRule = AdoptShellPermissionsRule(
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES
- )
+ @get:Rule
+ val mAdoptShellPermissionsRule =
+ AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ )
val mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
@Test
@@ -95,13 +97,14 @@ class PermissionServicePerfTest {
private fun dumpResult(
parser: TraceMarkParser,
- handler: BiConsumer<String, List<TraceMarkParser.TraceMarkSlice>>
+ handler: BiConsumer<String, List<TraceMarkParser.TraceMarkSlice>>,
) {
parser.reset()
try {
- val inputStream = ParcelFileDescriptor.AutoCloseInputStream(
- mUiAutomation.executeShellCommand(COMMAND_TRACE_DUMP)
- )
+ val inputStream =
+ ParcelFileDescriptor.AutoCloseInputStream(
+ mUiAutomation.executeShellCommand(COMMAND_TRACE_DUMP)
+ )
val reader = BufferedReader(InputStreamReader(inputStream))
var line = reader.readLine()
while (line != null) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index cc2d104813e4..d48af2cccd59 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -810,7 +810,7 @@ public class JobInfo implements Parcelable {
/**
* <p class="caution"><strong>Note:</strong> Beginning with
- * {@link android.os.Build.VERSION_CODES#B}, this flag will be ignored and no longer
+ * {@link android.os.Build.VERSION_CODES#BAKLAVA}, this flag will be ignored and no longer
* function effectively, regardless of the calling app's target SDK version.
* Calling this method will always return {@code false}.
*
@@ -2137,9 +2137,9 @@ public class JobInfo implements Parcelable {
* Jobs marked as important-while-foreground are given {@link #PRIORITY_HIGH} by default.
*
* <p class="caution"><strong>Note:</strong> Beginning with
- * {@link android.os.Build.VERSION_CODES#B}, this flag will be ignored and no longer
+ * {@link android.os.Build.VERSION_CODES#BAKLAVA}, this flag will be ignored and no longer
* function effectively, regardless of the calling app's target SDK version.
- * {link #isImportantWhileForeground()} will always return {@code false}.
+ * {@link #isImportantWhileForeground()} will always return {@code false}.
* Apps should use {link #setExpedited(boolean)} with {@code true} to indicate
* that this job is important and needs to run as soon as possible.
*
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 876274ecc32e..aae5bb31273b 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -126,3 +126,15 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "tune_quota_window_default_parameters"
+ namespace: "backstage_power"
+ description: "Tune default active/exempted bucket quota parameters"
+ bug: "401767691"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 251776e907d8..44e4999ccf44 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -5369,7 +5369,9 @@ public class AlarmManagerService extends SystemService {
// to do any wakelock or stats tracking, so we have nothing
// left to do here but go on to the next thing.
mSendFinishCount++;
- if (Flags.acquireWakelockBeforeSend()) {
+ if (Flags.acquireWakelockBeforeSend() && mBroadcastRefCount == 0) {
+ // No other alarms are in-flight and this dispatch failed. We will
+ // acquire the wakelock again before the next dispatch.
mWakeLock.release();
}
return;
@@ -5409,7 +5411,9 @@ public class AlarmManagerService extends SystemService {
// stats management to do. It threw before we posted the delayed
// timeout message, so we're done here.
mListenerFinishCount++;
- if (Flags.acquireWakelockBeforeSend()) {
+ if (Flags.acquireWakelockBeforeSend() && mBroadcastRefCount == 0) {
+ // No other alarms are in-flight and this dispatch failed. We will
+ // acquire the wakelock again before the next dispatch.
mWakeLock.release();
}
return;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index abec170f3b7d..d52bbc245157 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -18,6 +18,7 @@ package com.android.server.job.controllers;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -28,6 +29,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -56,7 +58,10 @@ public final class DeviceIdleJobsController extends StateController {
private static final boolean DEBUG = JobSchedulerService.DEBUG
|| Log.isLoggable(TAG, Log.DEBUG);
- private static final long BACKGROUND_JOBS_DELAY = 3000;
+ /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
+ private static final String DIJC_CONSTANT_PREFIX = "dijc_";
+ private static final String KEY_BACKGROUND_JOBS_DELAY_MS =
+ DIJC_CONSTANT_PREFIX + "background_jobs_delay_ms";
static final int PROCESS_BACKGROUND_JOBS = 1;
@@ -78,6 +83,8 @@ public final class DeviceIdleJobsController extends StateController {
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
+ private long mBackgroundJobsDelay;
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -128,6 +135,9 @@ public final class DeviceIdleJobsController extends StateController {
public DeviceIdleJobsController(JobSchedulerService service) {
super(service);
+ mBackgroundJobsDelay = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_jobSchedulerBackgroundJobsDelay);
+
mHandler = new DeviceIdleJobsDelayHandler(AppSchedulingModuleThread.get().getLooper());
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -165,7 +175,7 @@ public final class DeviceIdleJobsController extends StateController {
// When coming out of doze, process all foreground uids and EJs immediately,
// while others will be processed after a delay of 3 seconds.
mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor);
- mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
+ mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, mBackgroundJobsDelay);
}
}
// Inform the job scheduler service about idle mode changes
@@ -237,6 +247,26 @@ public final class DeviceIdleJobsController extends StateController {
}
@Override
+ public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {
+ switch (key) {
+ case KEY_BACKGROUND_JOBS_DELAY_MS:
+ mBackgroundJobsDelay = Math.max(0, properties.getLong(key, mBackgroundJobsDelay));
+ break;
+ }
+ }
+
+ @Override
+ public void dumpConstants(IndentingPrintWriter pw) {
+ pw.println();
+ pw.print(DeviceIdleJobsController.class.getSimpleName());
+ pw.println(":");
+ pw.increaseIndent();
+ pw.print(KEY_BACKGROUND_JOBS_DELAY_MS, mBackgroundJobsDelay).println();
+ pw.decreaseIndent();
+ }
+
+ @Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
pw.println("Idle mode: " + mDeviceIdleMode);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 54d337eded7d..a9c4a1501dd8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -360,13 +360,13 @@ public final class QuotaController extends StateController {
/** How much time each app will have to run jobs within their standby bucket window. */
private final long[] mAllowedTimePerPeriodMs = new long[]{
- QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS,
0, // NEVER
QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
- QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS
+ QcConstants.DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS
};
/**
@@ -3178,9 +3178,11 @@ public final class QuotaController extends StateController {
static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
- private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ // Legacy default time each app will have to run jobs within EXEMPTED bucket
+ private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
10 * 60 * 1000L; // 10 minutes
- private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ // Legacy default time each app will have to run jobs within ACTIVE bucket
+ private static final long DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS =
10 * 60 * 1000L; // 10 minutes
@@ -3192,14 +3194,26 @@ public final class QuotaController extends StateController {
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
10 * 60 * 1000L; // 10 minutes
+
+ // Current default time each app will have to run jobs within EXEMPTED bucket
+ private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ 20 * 60 * 1000L; // 20 minutes
+ // Current default time each app will have to run jobs within ACTIVE bucket
+ private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ 20 * 60 * 1000L; // 20 minutes
+ private static final long DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
+ 20 * 60 * 1000L; // 20 minutes
+
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
// Legacy default window size for EXEMPTED bucket
+ // EXEMPT apps can run jobs at any time
private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
// Legacy default window size for ACTIVE bucket
+ // ACTIVE apps can run jobs at any time
private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
// Legacy default window size for WORKING bucket
private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS =
2 * 60 * 60 * 1000L; // 2 hours
@@ -3216,6 +3230,13 @@ public final class QuotaController extends StateController {
private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS =
12 * 60 * 60 * 1000L; // 12 hours
+ // Latest default window size for EXEMPTED bucket.
+ private static final long DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS =
+ 40 * 60 * 1000L; // 40 minutes.
+ // Latest default window size for ACTIVE bucket.
+ private static final long DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS =
+ 60 * 60 * 1000L; // 60 minutes.
+
private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS =
@@ -3276,12 +3297,13 @@ public final class QuotaController extends StateController {
* bucket window.
*/
public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
/**
* How much time each app in the active bucket will have to run jobs within their standby
* bucket window.
*/
- public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
/**
* How much time each app in the working set bucket will have to run jobs within their
* standby bucket window.
@@ -3575,11 +3597,30 @@ public final class QuotaController extends StateController {
public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
void adjustDefaultBucketWindowSizes() {
- WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
- WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
+ ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS :
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;
+
+ WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
+ DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
+ WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
+ DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS;
WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS;
+ mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS));
+ mAllowedTimePerPeriodMs[ACTIVE_INDEX] = Math.min(MAX_PERIOD_MS,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS));
+
mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max(
mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS));
@@ -3592,6 +3633,11 @@ public final class QuotaController extends StateController {
mBucketPeriodsMs[FREQUENT_INDEX] = Math.max(
mAllowedTimePerPeriodMs[FREQUENT_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
+
+ mAllowedTimePeriodAdditionaInstallerMs =
+ Math.min(mBucketPeriodsMs[EXEMPTED_INDEX]
+ - mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
+ ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
}
void adjustDefaultEjLimits() {
@@ -3882,10 +3928,14 @@ public final class QuotaController extends StateController {
KEY_WINDOW_SIZE_RESTRICTED_MS);
ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS);
+ Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS);
ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS);
+ Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS :
+ DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS);
ALLOWED_TIME_PER_PERIOD_WORKING_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS);
@@ -3900,19 +3950,27 @@ public final class QuotaController extends StateController {
DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS);
ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
+ Flags.tuneQuotaWindowDefaultParameters()
+ ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS
+ : DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
DEFAULT_IN_QUOTA_BUFFER_MS);
MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
DEFAULT_MAX_EXECUTION_TIME_MS);
WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS,
- Flags.adjustQuotaDefaultConstants()
- ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
- DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS);
+ (Flags.adjustQuotaDefaultConstants()
+ && Flags.tuneQuotaWindowDefaultParameters())
+ ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
+ (Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS));
WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
- Flags.adjustQuotaDefaultConstants()
- ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
- DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS);
+ (Flags.adjustQuotaDefaultConstants()
+ && Flags.tuneQuotaWindowDefaultParameters())
+ ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
+ (Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS));
WINDOW_SIZE_WORKING_MS =
properties.getLong(KEY_WINDOW_SIZE_WORKING_MS,
Flags.adjustQuotaDefaultConstants()
diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index ce99bfed1ce3..cc02c8ae36e6 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -70,3 +70,10 @@ HSPLandroid/os/PerfettoTrackEventExtra$FieldString;->*
HSPLandroid/os/PerfettoTrackEventExtra$FieldNested;->*
HSPLandroid/os/PerfettoTrackEventExtra$Pool;->*
HSPLandroid/os/PerfettoTrackEventExtra$RingBuffer;->*
+
+# While the SystemFeaturesMetadata static cache isn't heavyweight, ensure it's
+# pre-initialized in the boot image to avoid redundant per-process overhead.
+# TODO(b/326623529): Consider removing this after the feature has fully ramped
+# and is captured with the boot image profiling pipeline.
+HSPLcom/android/internal/pm/SystemFeaturesMetadata;->*
+Lcom/android/internal/pm/SystemFeaturesMetadata;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index f87828ec8d9a..7f4b3244c164 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -11885,6 +11885,7 @@ com.android.internal.os.ZygoteServer$UsapPoolRefillAction
com.android.internal.os.ZygoteServer
com.android.internal.os.logging.MetricsLoggerWrapper
com.android.internal.pm.RoSystemFeatures
+com.android.internal.pm.SystemFeaturesMetadata
com.android.internal.pm.parsing.PackageParser2$Callback
com.android.internal.pm.parsing.PackageParserException
com.android.internal.pm.pkg.component.flags.FeatureFlags
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 696bc82a9ffc..378bb2d8e6b7 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -1138,6 +1138,10 @@ public class Bmgr {
}
out.append("]");
}
+ if (event.containsKey(BackupManagerMonitor.EXTRA_LOG_CANCELLATION_REASON)) {
+ out.append(" cancellationReason: ");
+ out.append(event.getInt(BackupManagerMonitor.EXTRA_LOG_CANCELLATION_REASON));
+ }
if (mVerbose) {
Set<String> remainingKeys = new ArraySet<>(event.keySet());
remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
@@ -1309,6 +1313,8 @@ public class Bmgr {
return "AGENT_FAILURE_DURING_RESTORE";
case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT:
return "FAILED_TO_READ_DATA_FROM_TRANSPORT";
+ case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN:
+ return "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN";
default:
return "UNKNOWN_ID";
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 844e52c3ecf2..b0070c5faa36 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -207,7 +207,7 @@ BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mLooper(new Looper(false)), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {
ATRACE_CALL();
- mSession = new SurfaceComposerClient();
+ mSession = sp<SurfaceComposerClient>::make();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 26e20f601c7a..6542d08d6384 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -89,6 +89,11 @@ public class UsbCommand extends Svc.Command {
IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
Context.USB_SERVICE));
+ if (usbMgr == null) {
+ System.err.println("Could not obtain USB service. Try again later.");
+ return;
+ }
+
Executor executor = context.getMainExecutor();
Consumer<Integer> consumer = new Consumer<Integer>(){
public void accept(Integer status){
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 4147fd7c4ae6..707acb00b102 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -11921,6 +11921,7 @@ com.android.internal.os.ZygoteServer$UsapPoolRefillAction
com.android.internal.os.ZygoteServer
com.android.internal.os.logging.MetricsLoggerWrapper
com.android.internal.pm.RoSystemFeatures
+com.android.internal.pm.SystemFeaturesMetadata
com.android.internal.pm.parsing.PackageParser2$Callback
com.android.internal.pm.parsing.PackageParserException
com.android.internal.pm.pkg.component.flags.FeatureFlags
diff --git a/core/api/current.txt b/core/api/current.txt
index 3da5a5cca861..4cd6d6f03ee8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -46681,16 +46681,16 @@ package android.telephony {
method public int getLastCauseCode();
method @Nullable public android.net.LinkProperties getLinkProperties();
method public int getNetworkType();
- method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus();
+ method public int getNetworkValidationStatus();
method public int getState();
method public int getTransportType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
- field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_FAILURE = 4; // 0x4
- field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; // 0x2
- field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; // 0x1
- field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_SUCCESS = 3; // 0x3
- field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; // 0x0
+ field public static final int NETWORK_VALIDATION_FAILURE = 4; // 0x4
+ field public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; // 0x2
+ field public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; // 0x1
+ field public static final int NETWORK_VALIDATION_SUCCESS = 3; // 0x3
+ field public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; // 0x0
}
public final class RadioAccessSpecifier implements android.os.Parcelable {
@@ -56108,6 +56108,17 @@ package android.view {
method @NonNull public android.view.WindowInsets getWindowInsets();
}
+ @FlaggedApi("android.xr.xr_manifest_entries") public final class XrWindowProperties {
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_ACTIVITY_START_MODE = "android.window.PROPERTY_XR_ACTIVITY_START_MODE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED = "android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED = "XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED = "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_HOME_SPACE = "XR_ACTIVITY_START_MODE_HOME_SPACE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_UNDEFINED = "XR_ACTIVITY_START_MODE_UNDEFINED";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_BOUNDARY_TYPE_LARGE = "XR_BOUNDARY_TYPE_LARGE";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_BOUNDARY_TYPE_NO_RECOMMENDATION = "XR_BOUNDARY_TYPE_NO_RECOMMENDATION";
+ }
+
}
package android.view.accessibility {
@@ -58276,7 +58287,9 @@ package android.view.inspector {
}
public final class WindowInspector {
+ method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void addGlobalWindowViewsListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.view.View>>);
method @NonNull public static java.util.List<android.view.View> getGlobalWindowViews();
+ method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void removeGlobalWindowViewsListener(@NonNull java.util.function.Consumer<java.util.List<android.view.View>>);
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 984bc680c685..b9d61cd334e3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2941,6 +2941,7 @@ package android.app.smartspace.uitemplatedata {
package android.app.supervision {
@FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
+ method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent();
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
}
@@ -11725,6 +11726,7 @@ package android.os {
field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST";
field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+ field @FlaggedApi("android.multiuser.allow_supervising_profile") public static final String USER_TYPE_PROFILE_SUPERVISING = "android.os.usertype.profile.SUPERVISING";
field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
}
@@ -16441,7 +16443,7 @@ package android.telephony.data {
method @Deprecated public int getMtu();
method public int getMtuV4();
method public int getMtuV6();
- method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus();
+ method public int getNetworkValidationStatus();
method @NonNull public java.util.List<java.net.InetAddress> getPcscfAddresses();
method public int getPduSessionId();
method public int getProtocolType();
@@ -16478,7 +16480,7 @@ package android.telephony.data {
method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int);
- method @FlaggedApi("com.android.internal.telephony.flags.network_validation") @NonNull public android.telephony.data.DataCallResponse.Builder setNetworkValidationStatus(int);
+ method @NonNull public android.telephony.data.DataCallResponse.Builder setNetworkValidationStatus(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>);
method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(@IntRange(from=android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET, to=15) int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int);
@@ -16558,7 +16560,7 @@ package android.telephony.data {
method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
- method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback);
@@ -16620,7 +16622,7 @@ package android.telephony.data {
method public final int getSlotIndex();
method public void reportEmergencyDataNetworkPreferredTransportChanged(int);
method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>);
- method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 00ec48b79541..4c8283907712 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1199,6 +1199,7 @@ package android.content.pm {
method public boolean isProfile();
method public boolean isQuietModeEnabled();
method public boolean isRestricted();
+ method @FlaggedApi("android.multiuser.allow_supervising_profile") public boolean isSupervisingProfile();
method public boolean supportsSwitchTo();
method @Deprecated public boolean supportsSwitchToByUser();
method public void writeToParcel(android.os.Parcel, int);
@@ -3031,6 +3032,7 @@ package android.provider {
field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services";
field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
field public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ field public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled";
field public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
field public static final String NOTIFICATION_BADGING = "notification_badging";
field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
@@ -4505,7 +4507,7 @@ package android.window {
method @NonNull public android.window.WindowContainerTransaction requestFocusOnTaskFragment(@NonNull android.os.IBinder);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
- method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+ method @Deprecated @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @NonNull android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 1ad247e24bc0..980d9737aba7 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -29,6 +29,15 @@ filegroup {
"android/os/*MessageQueue/**/*.java",
"android/ranging/**/*.java",
":dynamic_instrumentation_manager_aidl_sources",
+ "**/*_ravenwood.java",
+ ],
+ visibility: ["//frameworks/base"],
+}
+
+filegroup {
+ name: "framework-ravenwood-sources",
+ srcs: [
+ "**/*_ravenwood.java",
],
visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index d84a4c12a2cd..9f789328d8e0 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -110,7 +110,7 @@ public class AnimationHandler {
}
};
- public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
+ public static final ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
private static AnimationHandler sTestHandler = null;
private boolean mListDirty = false;
@@ -118,10 +118,12 @@ public class AnimationHandler {
if (sTestHandler != null) {
return sTestHandler;
}
- if (sAnimatorHandler.get() == null) {
- sAnimatorHandler.set(new AnimationHandler());
+ AnimationHandler animatorHandler = sAnimatorHandler.get();
+ if (animatorHandler == null) {
+ animatorHandler = new AnimationHandler();
+ sAnimatorHandler.set(animatorHandler);
}
- return sAnimatorHandler.get();
+ return animatorHandler;
}
/**
@@ -384,6 +386,12 @@ public class AnimationHandler {
});
}
+ void removePendingEndAnimationCallback(Runnable notifyEndAnimation) {
+ if (mPendingEndAnimationListeners != null) {
+ mPendingEndAnimationListeners.remove(notifyEndAnimation);
+ }
+ }
+
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 4bf87f91cb2f..e62cd556af9a 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -82,6 +82,12 @@ public abstract class Animator implements Cloneable {
static boolean sPostNotifyEndListenerEnabled;
/**
+ * If {@link #sPostNotifyEndListenerEnabled} is enabled, it will be set when the end callback
+ * is scheduled. It is cleared when it runs or finishes immediately, e.g. cancel.
+ */
+ private Runnable mPendingEndCallback;
+
+ /**
* A cache of the values in a list. Used so that when calling the list, we have a copy
* of it in case the list is modified while iterating. The array can be reused to avoid
* allocation on every notification.
@@ -660,10 +666,33 @@ public abstract class Animator implements Cloneable {
}
}
+ /**
+ * This is called when the animator needs to finish immediately. This is usually no-op unless
+ * {@link #sPostNotifyEndListenerEnabled} is enabled and a finish request calls around the last
+ * animation frame.
+ *
+ * @param notifyListeners Whether to invoke {@link AnimatorListener#onAnimationEnd}.
+ * @return {@code true} if the pending listeners are removed.
+ */
+ boolean consumePendingEndListeners(boolean notifyListeners) {
+ if (mPendingEndCallback == null) {
+ return false;
+ }
+ AnimationHandler.getInstance().removePendingEndAnimationCallback(mPendingEndCallback);
+ mPendingEndCallback = null;
+ if (notifyListeners) {
+ notifyEndListeners(false /* isReversing */);
+ }
+ return true;
+ }
+
void notifyEndListenersFromEndAnimation(boolean isReversing, boolean postNotifyEndListener) {
if (postNotifyEndListener) {
- AnimationHandler.getInstance().postEndAnimationCallback(
- () -> completeEndAnimation(isReversing, "postNotifyAnimEnd"));
+ mPendingEndCallback = () -> {
+ completeEndAnimation(isReversing, "postNotifyAnimEnd");
+ mPendingEndCallback = null;
+ };
+ AnimationHandler.getInstance().postEndAnimationCallback(mPendingEndCallback);
} else {
completeEndAnimation(isReversing, "notifyAnimEnd");
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 78566d2fe98d..4a07de0410ae 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -423,6 +423,13 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
notifyListeners(AnimatorCaller.ON_CANCEL, false);
callOnPlayingSet(Animator::cancel);
mPlayingSet.clear();
+ // If the end callback is pending, invoke the end callbacks of the animator nodes before
+ // ending this set. Pass notifyListeners=false because this endAnimation will do that.
+ if (consumePendingEndListeners(false /* notifyListeners */)) {
+ for (int i = mNodeMap.size() - 1; i >= 0; i--) {
+ mNodeMap.keyAt(i).consumePendingEndListeners(true /* notifyListeners */);
+ }
+ }
endAnimation();
}
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 492c2ffc561f..fbcc73ea59e7 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1182,6 +1182,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
// If end has already been requested, through a previous end() or cancel() call, no-op
// until animation starts again.
if (mAnimationEndRequested) {
+ consumePendingEndListeners(true /* notifyListeners */);
return;
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b4f653354e07..d5df48a2ea22 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -10060,9 +10060,11 @@ public class Activity extends ContextThemeWrapper
}
});
if (mJankTracker == null) {
- // TODO b/377960907 use the Choreographer attached to the ViewRootImpl instead.
- mJankTracker = new JankTracker(Choreographer.getInstance(),
- decorView);
+ if (android.app.jank.Flags.viewrootChoreographer()) {
+ mJankTracker = new JankTracker(decorView);
+ } else {
+ mJankTracker = new JankTracker(Choreographer.getInstance(), decorView);
+ }
}
// TODO b/377674765 confirm this is the string we want logged.
mJankTracker.setActivityName(getComponentName().getClassName());
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f9ec214390f9..62816a2fa0f5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -55,9 +55,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.drawable.Icon;
-import android.hardware.HardwareBuffer;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
@@ -86,7 +84,6 @@ import android.util.Log;
import android.util.Singleton;
import android.util.Size;
import android.view.WindowInsetsController.Appearance;
-import android.window.TaskSnapshot;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.LocalePicker;
@@ -3102,7 +3099,8 @@ public class ActivityManager {
/**
* Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
* activity along with the task, so it is positioned immediately behind
- * the task.
+ * the task. This flag is ignored if the task's windowing mode is
+ * {@link WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW}.
*/
public static final int MOVE_TASK_WITH_HOME = 0x00000001;
@@ -4975,6 +4973,25 @@ public class ActivityManager {
}
/**
+ * Fully stop the given app's processes without restoring service starts or
+ * bindings, but without the other durable effects of the full-scale
+ * "force stop" intervention.
+ *
+ * @param packageName The name of the package to be stopped.
+ *
+ * @hide This is not available to third party applications due to
+ * it allowing them to break other applications by stopping their
+ * services.
+ */
+ public void stopPackageForUser(String packageName) {
+ try {
+ getService().stopAppForUser(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the current locales of the device. Calling app must have the permission
* {@code android.permission.CHANGE_CONFIGURATION} and
* {@code android.permission.WRITE_SETTINGS}.
@@ -5401,10 +5418,11 @@ public class ActivityManager {
*
* @hide
*/
+ @Nullable
@RequiresPermission(Manifest.permission.MANAGE_USERS)
- public @Nullable String getSwitchingFromUserMessage() {
+ public String getSwitchingFromUserMessage(@UserIdInt int userId) {
try {
- return getService().getSwitchingFromUserMessage();
+ return getService().getSwitchingFromUserMessage(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -5415,10 +5433,11 @@ public class ActivityManager {
*
* @hide
*/
+ @Nullable
@RequiresPermission(Manifest.permission.MANAGE_USERS)
- public @Nullable String getSwitchingToUserMessage() {
+ public String getSwitchingToUserMessage(@UserIdInt int userId) {
try {
- return getService().getSwitchingToUserMessage();
+ return getService().getSwitchingToUserMessage(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a12c0674998e..e5f7889859c1 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -292,14 +292,14 @@ public abstract class ActivityManagerInternal {
public abstract boolean canStartMoreUsers();
/**
- * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}.
+ * Sets the user switcher message for switching from a user.
*/
- public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage);
+ public abstract void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message);
/**
- * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}.
+ * Sets the user switcher message for switching to a user.
*/
- public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage);
+ public abstract void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message);
/**
* Returns maximum number of users that can run simultaneously.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4c9116b02c1d..0a2b1eaaad6b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -273,6 +273,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.nio.file.DirectoryStream;
@@ -2268,10 +2269,16 @@ public final class ActivityThread extends ClientTransactionHandler
public void getExecutableMethodFileOffsets(
@NonNull MethodDescriptor methodDescriptor,
@NonNull IOffsetCallback resultCallback) {
- Method method = MethodDescriptorParser.parseMethodDescriptor(
+ Executable executable = MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(), methodDescriptor);
- VMDebug.ExecutableMethodFileOffsets location =
- VMDebug.getExecutableMethodFileOffsets(method);
+ VMDebug.ExecutableMethodFileOffsets location;
+ if (com.android.art.flags.Flags.executableMethodFileOffsetsV2()) {
+ location = VMDebug.getExecutableMethodFileOffsets(executable);
+ } else if (executable instanceof Method) {
+ location = VMDebug.getExecutableMethodFileOffsets((Method) executable);
+ } else {
+ throw new UnsupportedOperationException();
+ }
try {
if (location == null) {
resultCallback.onResult(null);
@@ -3995,6 +4002,10 @@ public final class ActivityThread extends ClientTransactionHandler
+ (fromIpc ? " (from ipc" : ""));
}
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "updateProcessState: processState=" + processState);
+ }
}
/** Converts a process state to a VM process state. */
@@ -7362,16 +7373,6 @@ public final class ActivityThread extends ClientTransactionHandler
}
WindowManagerGlobal.getInstance().trimMemory(level);
-
- if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
- unscheduleGcIdler();
- doGcIfNeeded("tm");
- }
- if (SystemProperties.getInt("debug.am.run_mallopt_trim_level", Integer.MAX_VALUE)
- <= level) {
- unschedulePurgeIdler();
- purgePendingResources();
- }
}
private void setupGraphicsSupport(Context context) {
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 599f1a8be233..3fd9d8b26611 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -102,6 +102,10 @@ public class AppCompatTaskInfo implements Parcelable {
private static final int FLAG_FULLSCREEN_OVERRIDE_USER = FLAG_BASE << 8;
/** Top activity flag for whether min aspect ratio of the activity has been overridden.*/
public static final int FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE = FLAG_BASE << 9;
+ /** Top activity flag for whether restart menu is shown due to display move. */
+ private static final int FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE = FLAG_BASE << 10;
+ /** Top activity flag for whether activity opted out of edge to edge. */
+ public static final int FLAG_OPT_OUT_EDGE_TO_EDGE = FLAG_BASE << 11;
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
@@ -115,7 +119,9 @@ public class AppCompatTaskInfo implements Parcelable {
FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON,
FLAG_FULLSCREEN_OVERRIDE_SYSTEM,
FLAG_FULLSCREEN_OVERRIDE_USER,
- FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE
+ FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE,
+ FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE,
+ FLAG_OPT_OUT_EDGE_TO_EDGE
})
public @interface TopActivityFlag {}
@@ -129,11 +135,13 @@ public class AppCompatTaskInfo implements Parcelable {
@TopActivityFlag
private static final int FLAGS_ORGANIZER_INTERESTED = FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP
| FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON | FLAG_FULLSCREEN_OVERRIDE_SYSTEM
- | FLAG_FULLSCREEN_OVERRIDE_USER | FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE;
+ | FLAG_FULLSCREEN_OVERRIDE_USER | FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE
+ | FLAG_OPT_OUT_EDGE_TO_EDGE;
@TopActivityFlag
private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED
- | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED;
+ | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED
+ | FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE;
private AppCompatTaskInfo() {
// Do nothing
@@ -300,6 +308,21 @@ public class AppCompatTaskInfo implements Parcelable {
}
/**
+ * @return {@code true} if the restart menu is enabled for the top activity due to display move.
+ */
+ public boolean isRestartMenuEnabledForDisplayMove() {
+ return isTopActivityFlagEnabled(FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE);
+ }
+
+ /**
+ * Sets the top activity flag for whether the restart menu is enabled for the top activity due
+ * to display move.
+ */
+ public void setRestartMenuEnabledForDisplayMove(boolean enable) {
+ setTopActivityFlag(FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE, enable);
+ }
+
+ /**
* @return {@code true} if the top activity bounds are letterboxed.
*/
public boolean isTopActivityLetterboxed() {
@@ -328,6 +351,20 @@ public class AppCompatTaskInfo implements Parcelable {
setTopActivityFlag(FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE, enable);
}
+ /**
+ * Sets the top activity flag for whether the activity has opted out of edge to edge.
+ */
+ public void setOptOutEdgeToEdge(boolean enable) {
+ setTopActivityFlag(FLAG_OPT_OUT_EDGE_TO_EDGE, enable);
+ }
+
+ /**
+ * @return {@code true} if the top activity has opted out of edge to edge.
+ */
+ public boolean hasOptOutEdgeToEdge() {
+ return isTopActivityFlagEnabled(FLAG_OPT_OUT_EDGE_TO_EDGE);
+ }
+
/** Clear all top activity flags and set to false. */
public void clearTopActivityFlags() {
mTopActivityFlags = FLAG_UNDEFINED;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 248f191cb8b8..1864d4a55f2e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -18,7 +18,6 @@ package android.app;
import static android.location.flags.Flags.FLAG_LOCATION_BYPASS;
-import static android.media.audio.Flags.roForegroundAudioControl;
import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
@@ -3481,6 +3480,16 @@ public class AppOpsManager {
}
/**
+ * Whether an app op is backed by a runtime permission or not.
+ * @hide
+ */
+ public static boolean opIsRuntimePermission(int op) {
+ if (op == OP_NONE) return false;
+
+ return ArrayUtils.contains(RUNTIME_PERMISSION_OPS, op);
+ }
+
+ /**
* Retrieve the user restriction associated with an operation, or null if there is not one.
* @hide
*/
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 2daa52b47102..fa977c93113a 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -228,7 +228,7 @@ public final class AutomaticZenRule implements Parcelable {
public AutomaticZenRule(Parcel source) {
enabled = source.readInt() == ENABLED;
if (source.readInt() == ENABLED) {
- name = getTrimmedString(source.readString8());
+ name = getTrimmedString(source.readString());
}
interruptionFilter = source.readInt();
conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class));
@@ -238,11 +238,11 @@ public final class AutomaticZenRule implements Parcelable {
source.readParcelable(null, android.content.ComponentName.class));
creationTime = source.readLong();
mZenPolicy = source.readParcelable(null, ZenPolicy.class);
- mPkg = source.readString8();
+ mPkg = source.readString();
mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
mAllowManualInvocation = source.readBoolean();
mIconResId = source.readInt();
- mTriggerDescription = getTrimmedString(source.readString8(), MAX_DESC_LENGTH);
+ mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
mType = source.readInt();
}
@@ -514,7 +514,7 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeInt(enabled ? ENABLED : DISABLED);
if (name != null) {
dest.writeInt(1);
- dest.writeString8(name);
+ dest.writeString(name);
} else {
dest.writeInt(0);
}
@@ -524,11 +524,11 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeParcelable(configurationActivity, 0);
dest.writeLong(creationTime);
dest.writeParcelable(mZenPolicy, 0);
- dest.writeString8(mPkg);
+ dest.writeString(mPkg);
dest.writeParcelable(mDeviceEffects, 0);
dest.writeBoolean(mAllowManualInvocation);
dest.writeInt(mIconResId);
- dest.writeString8(mTriggerDescription);
+ dest.writeString(mTriggerDescription);
dest.writeInt(mType);
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index dc5974fde0b0..99a2763650cd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2473,11 +2473,9 @@ class ContextImpl extends Context {
@Override
public int getPermissionRequestState(String permission) {
Objects.requireNonNull(permission, "Permission name can't be null");
- int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(),
- permission);
PermissionManager permissionManager = getSystemService(PermissionManager.class);
return permissionManager.getPermissionRequestState(getOpPackageName(), permission,
- deviceId);
+ getDeviceId());
}
@Override
@@ -2944,7 +2942,11 @@ class ContextImpl extends Context {
private void updateResourceOverlayConstraints() {
if (mResources != null) {
- mResources.getAssets().setOverlayConstraints(getDisplayId(), getDeviceId());
+ // Avoid calling getDisplay() here, as it makes a binder call into
+ // DisplayManagerService if the relevant DisplayInfo is not cached in
+ // DisplayManagerGlobal.
+ int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+ mResources.getAssets().setOverlayConstraints(displayId, getDeviceId());
}
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ad01ad57b2d8..6cdfb97520ae 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -403,8 +403,8 @@ interface IActivityManager {
void setPackageScreenCompatMode(in String packageName, int mode);
@UnsupportedAppUsage
boolean switchUser(int userid);
- String getSwitchingFromUserMessage();
- String getSwitchingToUserMessage();
+ String getSwitchingFromUserMessage(int userId);
+ String getSwitchingToUserMessage(int userId);
@UnsupportedAppUsage
void setStopUserOnSwitch(int value);
boolean removeTask(int taskId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cc72d8f1ad8c..7c293cb9cb3b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3266,7 +3266,6 @@ public class Notification implements Parcelable
final Class<? extends Style> notificationStyle = getNotificationStyle();
return notificationStyle == null
- || BigPictureStyle.class.equals(notificationStyle)
|| BigTextStyle.class.equals(notificationStyle)
|| CallStyle.class.equals(notificationStyle)
|| ProgressStyle.class.equals(notificationStyle);
@@ -6144,6 +6143,20 @@ public class Notification implements Parcelable
result.mTitleMarginSet.applyToView(contentView, p.mTextViewId);
contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1);
}
+ // The expand button uses paddings rather than margins, so we'll adjust it
+ // separately.
+ adjustExpandButtonPadding(contentView, result.mRightIconVisible);
+ }
+
+ private void adjustExpandButtonPadding(RemoteViews contentView, boolean rightIconVisible) {
+ if (notificationsRedesignTemplates()) {
+ final Resources res = mContext.getResources();
+ int normalPadding = res.getDimensionPixelSize(R.dimen.notification_2025_margin);
+ int iconSpacing = res.getDimensionPixelSize(
+ R.dimen.notification_2025_expand_button_right_icon_spacing);
+ contentView.setInt(R.id.expand_button, "setStartPadding",
+ rightIconVisible ? iconSpacing : normalPadding);
+ }
}
// This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
@@ -6155,12 +6168,21 @@ public class Notification implements Parcelable
@NonNull TemplateBindResult result) {
final Resources resources = mContext.getResources();
final float density = resources.getDisplayMetrics().density;
- final float iconMarginDp = resources.getDimension(
- R.dimen.notification_right_icon_content_margin) / density;
+ int iconMarginId = notificationsRedesignTemplates()
+ ? R.dimen.notification_2025_right_icon_content_margin
+ : R.dimen.notification_right_icon_content_margin;
+ final float iconMarginDp = resources.getDimension(iconMarginId) / density;
final float contentMarginDp = resources.getDimension(
R.dimen.notification_content_margin_end) / density;
- final float expanderSizeDp = resources.getDimension(
- R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
+ float spaceForExpanderDp;
+ if (notificationsRedesignTemplates()) {
+ spaceForExpanderDp = resources.getDimension(
+ R.dimen.notification_2025_right_icon_expanded_margin_end) / density
+ - contentMarginDp;
+ } else {
+ spaceForExpanderDp = resources.getDimension(
+ R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
+ }
final float viewHeightDp = resources.getDimension(
R.dimen.notification_right_icon_size) / density;
float viewWidthDp = viewHeightDp; // icons are 1:1 by default
@@ -6177,9 +6199,10 @@ public class Notification implements Parcelable
}
}
}
+ // Margin needed for the header to accommodate the icon when shown
final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
- viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp);
+ viewHeightDp, extraMarginEndDpIfVisible, spaceForExpanderDp);
}
/**
@@ -6430,45 +6453,49 @@ public class Notification implements Parcelable
return mN.showsTime() || mN.showsChronometer();
}
- private void resetStandardTemplateWithActions(RemoteViews big) {
+ private void resetStandardTemplateWithActions(RemoteViews contentView) {
// actions_container is only reset when there are no actions to avoid focus issues with
// remote inputs.
- big.setViewVisibility(R.id.actions, View.GONE);
- big.removeAllViews(R.id.actions);
+ contentView.setViewVisibility(R.id.actions, View.GONE);
+ contentView.removeAllViews(R.id.actions);
- big.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
- big.setTextViewText(R.id.notification_material_reply_text_1, null);
- big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE);
- big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
+ contentView.setViewVisibility(R.id.notification_material_reply_container, View.GONE);
+ contentView.setTextViewText(R.id.notification_material_reply_text_1, null);
+ contentView.setViewVisibility(R.id.notification_material_reply_text_1_container,
+ View.GONE);
+ contentView.setViewVisibility(R.id.notification_material_reply_progress, View.GONE);
- big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
- big.setTextViewText(R.id.notification_material_reply_text_2, null);
- big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
- big.setTextViewText(R.id.notification_material_reply_text_3, null);
+ contentView.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE);
+ contentView.setTextViewText(R.id.notification_material_reply_text_2, null);
+ contentView.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
+ contentView.setTextViewText(R.id.notification_material_reply_text_3, null);
- // This may get erased by bindSnoozeAction
- big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
- RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
+ if (!notificationsRedesignTemplates()) {
+ // This may get erased by bindSnoozeAction, or if we're showing the bubble icon
+ contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
+ }
}
- private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
+ private boolean bindSnoozeAction(RemoteViews contentView, StandardTemplateParams p) {
boolean hideSnoozeButton = mN.isFgsOrUij()
|| mN.fullScreenIntent != null
|| isBackgroundColorized(p)
|| p.mViewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
- big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
+ contentView.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
if (hideSnoozeButton) {
// Only hide; NotificationContentView will show it when it adds the click listener
- big.setViewVisibility(R.id.snooze_button, View.GONE);
+ contentView.setViewVisibility(R.id.snooze_button, View.GONE);
}
final boolean snoozeEnabled = !hideSnoozeButton
&& mContext.getContentResolver() != null
&& isSnoozeSettingEnabled();
- if (snoozeEnabled) {
- big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ if (!notificationsRedesignTemplates() && snoozeEnabled) {
+ contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
RemoteViews.MARGIN_BOTTOM, 0);
}
+ return snoozeEnabled;
}
private boolean isSnoozeSettingEnabled() {
@@ -6503,16 +6530,14 @@ public class Notification implements Parcelable
private RemoteViews applyStandardTemplateWithActions(int layoutId,
StandardTemplateParams p, TemplateBindResult result) {
- RemoteViews big = applyStandardTemplate(layoutId, p, result);
+ RemoteViews contentView = applyStandardTemplate(layoutId, p, result);
- resetStandardTemplateWithActions(big);
- bindSnoozeAction(big, p);
+ resetStandardTemplateWithActions(contentView);
+ boolean snoozeEnabled = bindSnoozeAction(contentView, p);
// color the snooze and bubble actions with the theme color
ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p));
- big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
- big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
-
- boolean validRemoteInput = false;
+ contentView.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
+ contentView.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
// In the UI, contextual actions appear separately from the standard actions, so we
// filter them out here.
@@ -6526,47 +6551,38 @@ public class Notification implements Parcelable
if (p.mCallStyleActions) {
// Clear view padding to allow buttons to start on the left edge.
// This must be done before 'setEmphasizedMode' which sets top/bottom margins.
- big.setViewPadding(R.id.actions, 0, 0, 0, 0);
+ contentView.setViewPadding(R.id.actions, 0, 0, 0, 0);
if (!Flags.notificationsRedesignTemplates()) {
// Add an optional indent that will make buttons start at the correct column
// when there is enough space to do so (and fall back to the left edge if not).
// This is handled directly in NotificationActionListLayout in the new design.
- big.setInt(R.id.actions, "setCollapsibleIndentDimen",
+ contentView.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
}
if (evenlyDividedCallStyleActionLayout()) {
if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
Log.d(TAG, "setting evenly divided mode on action list");
}
- big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
+ contentView.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
}
}
- big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
- if (numActions > 0 && !p.mHideActions) {
- big.setViewVisibility(R.id.actions_container, View.VISIBLE);
- big.setViewVisibility(R.id.actions, View.VISIBLE);
- big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
- RemoteViews.MARGIN_BOTTOM, 0);
- for (int i = 0; i < numActions; i++) {
- Action action = nonContextualActions.get(i);
-
- boolean actionHasValidInput = hasValidRemoteInput(action);
- validRemoteInput |= actionHasValidInput;
+ if (!notificationsRedesignTemplates()) {
+ contentView.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
+ }
- final RemoteViews button = generateActionButton(action, emphasizedMode, p);
- if (actionHasValidInput && !emphasizedMode) {
- // Clear the drawable
- button.setInt(R.id.action0, "setBackgroundResource", 0);
- }
- if (emphasizedMode && i > 0) {
- // Clear start margin from non-first buttons to reduce the gap between them.
- // (8dp remaining gap is from all buttons' standard 4dp inset).
- button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
- }
- big.addView(R.id.actions, button);
- }
+ boolean validRemoteInput = false;
+ // With the new design, the actions_container should always be visible to act as padding
+ // when there are no actions. We're making its child GONE instead.
+ int actionsContainerForVisibilityChange = notificationsRedesignTemplates()
+ ? R.id.actions_container_layout : R.id.actions_container;
+ if (numActions > 0 && !p.mHideActions) {
+ contentView.setViewVisibility(actionsContainerForVisibilityChange, View.VISIBLE);
+ contentView.setViewVisibility(R.id.actions, View.VISIBLE);
+ updateMarginsForActions(contentView, emphasizedMode);
+ validRemoteInput = populateActionsContainer(contentView, p, nonContextualActions,
+ numActions, emphasizedMode);
} else {
- big.setViewVisibility(R.id.actions_container, View.GONE);
+ contentView.setViewVisibility(actionsContainerForVisibilityChange, View.GONE);
}
RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
@@ -6575,37 +6591,89 @@ public class Notification implements Parcelable
&& !TextUtils.isEmpty(replyText[0].getText())
&& p.maxRemoteInputHistory > 0) {
boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
- big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
- big.setViewVisibility(R.id.notification_material_reply_text_1_container,
+ contentView.setViewVisibility(R.id.notification_material_reply_container,
+ View.VISIBLE);
+ contentView.setViewVisibility(R.id.notification_material_reply_text_1_container,
View.VISIBLE);
- big.setTextViewText(R.id.notification_material_reply_text_1,
+ contentView.setTextViewText(R.id.notification_material_reply_text_1,
ensureColorSpanContrastOrStripStyling(replyText[0].getText(), p));
- setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
- big.setViewVisibility(R.id.notification_material_reply_progress,
+ setTextViewColorSecondary(contentView, R.id.notification_material_reply_text_1, p);
+ contentView.setViewVisibility(R.id.notification_material_reply_progress,
showSpinner ? View.VISIBLE : View.GONE);
- big.setProgressIndeterminateTintList(
+ contentView.setProgressIndeterminateTintList(
R.id.notification_material_reply_progress,
ColorStateList.valueOf(getPrimaryAccentColor(p)));
if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
&& p.maxRemoteInputHistory > 1) {
- big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
- big.setTextViewText(R.id.notification_material_reply_text_2,
+ contentView.setViewVisibility(R.id.notification_material_reply_text_2,
+ View.VISIBLE);
+ contentView.setTextViewText(R.id.notification_material_reply_text_2,
ensureColorSpanContrastOrStripStyling(replyText[1].getText(), p));
- setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
+ setTextViewColorSecondary(contentView, R.id.notification_material_reply_text_2,
+ p);
if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
&& p.maxRemoteInputHistory > 2) {
- big.setViewVisibility(
+ contentView.setViewVisibility(
R.id.notification_material_reply_text_3, View.VISIBLE);
- big.setTextViewText(R.id.notification_material_reply_text_3,
+ contentView.setTextViewText(R.id.notification_material_reply_text_3,
ensureColorSpanContrastOrStripStyling(replyText[2].getText(), p));
- setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
+ setTextViewColorSecondary(contentView,
+ R.id.notification_material_reply_text_3, p);
}
}
}
- return big;
+ return contentView;
+ }
+
+ private void updateMarginsForActions(RemoteViews contentView, boolean emphasizedMode) {
+ if (notificationsRedesignTemplates()) {
+ if (emphasizedMode) {
+ // Emphasized actions look similar to smart replies, so let's use the same
+ // margins.
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_TOP,
+ R.dimen.notification_2025_smart_reply_container_margin);
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_BOTTOM,
+ R.dimen.notification_2025_smart_reply_container_margin);
+ } else {
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_TOP, 0);
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_BOTTOM,
+ R.dimen.notification_2025_action_list_margin_bottom);
+ }
+ } else {
+ contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, 0);
+ }
+ }
+
+ private boolean populateActionsContainer(RemoteViews contentView, StandardTemplateParams p,
+ List<Action> nonContextualActions, int numActions, boolean emphasizedMode) {
+ boolean validRemoteInput = false;
+ for (int i = 0; i < numActions; i++) {
+ Action action = nonContextualActions.get(i);
+
+ boolean actionHasValidInput = hasValidRemoteInput(action);
+ validRemoteInput |= actionHasValidInput;
+
+ final RemoteViews button = generateActionButton(action, emphasizedMode, p);
+ if (actionHasValidInput && !emphasizedMode) {
+ // Clear the drawable
+ button.setInt(R.id.action0, "setBackgroundResource", 0);
+ }
+ if (emphasizedMode && i > 0) {
+ // Clear start margin from non-first buttons to reduce the gap between them.
+ // (8dp remaining gap is from all buttons' standard 4dp inset).
+ button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
+ }
+ contentView.addView(R.id.actions, button);
+ }
+ return validRemoteInput;
}
/**
@@ -14659,13 +14727,19 @@ public class Notification implements Parcelable
public final MarginSet mTitleMarginSet = new MarginSet();
public void setRightIconState(boolean visible, float widthDp, float heightDp,
- float marginEndDpIfVisible, float expanderSizeDp) {
+ float marginEndDpIfVisible, float spaceForExpanderDp) {
mRightIconVisible = visible;
mRightIconWidthDp = widthDp;
mRightIconHeightDp = heightDp;
- mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
- mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
- mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
+ mHeadingExtraMarginSet.setValues(
+ /* valueIfGone = */ 0,
+ /* valueIfVisible = */ marginEndDpIfVisible);
+ mHeadingFullMarginSet.setValues(
+ /* valueIfGone = */ spaceForExpanderDp,
+ /* valueIfVisible = */ marginEndDpIfVisible + spaceForExpanderDp);
+ mTitleMarginSet.setValues(
+ /* valueIfGone = */ 0,
+ /* valueIfVisible = */ marginEndDpIfVisible + spaceForExpanderDp);
}
/**
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 6936ddc5eeac..59247934ba46 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -655,8 +655,10 @@ public class TaskInfo {
+ " effectiveUid=" + effectiveUid
+ " displayId=" + displayId
+ " isRunning=" + isRunning
- + " baseIntent=" + baseIntent + " baseActivity=" + baseActivity
- + " topActivity=" + topActivity + " origActivity=" + origActivity
+ + " baseIntent=" + baseIntent
+ + " baseActivity=" + baseActivity
+ + " topActivity=" + topActivity
+ + " origActivity=" + origActivity
+ " realActivity=" + realActivity
+ " numActivities=" + numActivities
+ " lastActiveTime=" + lastActiveTime
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index cb2b8adae0f9..8a8877f441a9 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -558,6 +558,11 @@ public final class DeviceAdminInfo implements Parcelable {
}
public void dump(Printer pw, String prefix) {
+ pw.println("mVisible: " + mVisible);
+ pw.println("mUsesPolicies: " + mUsesPolicies);
+ pw.println("mSupportsTransferOwnership: " + mSupportsTransferOwnership);
+ pw.println("mHeadlessDeviceOwnerMode: " + mHeadlessDeviceOwnerMode);
+
pw.println(prefix + "Receiver:");
mActivityInfo.dump(pw, prefix + " ");
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5359ba44a3d2..c74bd1a092ee 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6981,6 +6981,8 @@ public class DevicePolicyManager {
* <p>The caller must hold the
* {@link android.Manifest.permission#TRIGGER_LOST_MODE} permission.
*
+ * <p>This API accesses the device's location and will only be used when a device is lost.
+ *
* <p>Register a broadcast receiver to receive lost mode location updates. This receiver should
* subscribe to the {@link #ACTION_LOST_MODE_LOCATION_UPDATE} action and receive the location
* from an intent extra {@link #EXTRA_LOST_MODE_LOCATION}.
@@ -8269,6 +8271,7 @@ public class DevicePolicyManager {
*
* @throws SecurityException if the caller is not a device owner, a profile owner or
* delegated certificate chooser.
+ * @throws IllegalArgumentException if {@code alias} does not correspond to an existing key
* @see #grantKeyPairToWifiAuth
*/
public boolean isKeyPairGrantedToWifiAuth(@NonNull String alias) {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 0ecd2754b1f0..572bffe6c6a4 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -402,3 +402,13 @@ flag {
description: "Add new API for secondary lockscreen"
bug: "336297680"
}
+
+flag {
+ name: "remove_managed_esim_on_work_profile_deletion"
+ namespace: "enterprise"
+ description: "Remove managed eSIM when work profile is deleted"
+ bug: "347925470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index 83abc048af8a..e05ede580d3f 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -31,6 +31,7 @@ import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
import android.app.appsearch.JoinSpec;
+import android.app.appsearch.PropertyPath;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
@@ -141,6 +142,9 @@ public class AppFunctionManagerHelper {
.addFilterSchemas(
AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
targetPackage))
+ .addProjectionPaths(
+ SearchSpec.SCHEMA_TYPE_WILDCARD,
+ List.of(new PropertyPath(STATIC_PROPERTY_ENABLED_BY_DEFAULT)))
.setJoinSpec(joinSpec)
.setVerbatimSearchEnabled(true)
.build();
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index e741bc2bf608..ca14fce6706a 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -164,6 +164,15 @@ public class BackupManagerMonitor {
public static final String EXTRA_LOG_V_TO_U_ALLOWLIST =
"android.app.backup.extra.V_TO_U_ALLOWLIST";
+ /**
+ * An int indicating why a backup was cancelled. One of {@link
+ * com.android.server.backup.BackupRestoreTask.CancellationReason}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_LOG_CANCELLATION_REASON =
+ "android.app.backup.extra.CANCELLATION_REASON";
+
// TODO complete this list with all log messages. And document properly.
public static final int LOG_EVENT_ID_FULL_BACKUP_CANCEL = 4;
public static final int LOG_EVENT_ID_ILLEGAL_KEY = 5;
@@ -297,6 +306,9 @@ public class BackupManagerMonitor {
@hide */
public static final int LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT = 81;
+ /** The pipe between the BackupAgent and the framework was broken during full backup. @hide */
+ public static final int LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN = 82;
+
/**
* This method will be called each time something important happens on BackupManager.
*
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index 7718d159896e..3c8b1a0482ad 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -366,7 +366,7 @@ public class JankDataProcessor {
30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
Integer.MAX_VALUE
};
- private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length];
+ private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length - 1];
// Histogram of frame duration overruns encoded in predetermined buckets.
public PendingJankStat() {
@@ -511,7 +511,7 @@ public class JankDataProcessor {
if (overrunTime <= 1000) {
return (overrunTime - 200) / 100 + 43;
}
- return sFrameOverrunHistogramBounds.length - 1;
+ return mFrameOverrunBuckets.length - 1;
}
}
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
index 9c85b09f6be3..e3f67811757c 100644
--- a/core/java/android/app/jank/JankTracker.java
+++ b/core/java/android/app/jank/JankTracker.java
@@ -25,6 +25,7 @@ import android.view.AttachedSurfaceControl;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,7 +101,7 @@ public class JankTracker {
public void run() {
mDecorView.getViewTreeObserver()
.removeOnWindowAttachListener(mOnWindowAttachListener);
- registerForJankData();
+ initializeJankTrackingComponents();
}
}, REGISTRATION_DELAY_MS);
}
@@ -115,6 +116,7 @@ public class JankTracker {
}
};
+ // TODO remove this once the viewroot_choreographer bugfix has been rolled out. b/399724640
public JankTracker(Choreographer choreographer, View decorView) {
mStateTracker = new StateTracker(choreographer);
mJankDataProcessor = new JankDataProcessor(mStateTracker);
@@ -124,6 +126,19 @@ public class JankTracker {
}
/**
+ * Using this constructor delays the instantiation of the StateTracker and JankDataProcessor
+ * until after the OnWindowAttachListener is fired and the instance of Choreographer attached to
+ * the ViewRootImpl can be passed to StateTracker. This should ensures the vsync ids we are
+ * using to keep track of active states line up with the ids that are being returned by
+ * OnJankDataListener.
+ */
+ public JankTracker(View decorView) {
+ mDecorView = decorView;
+ mHandlerThread.start();
+ registerWindowListeners();
+ }
+
+ /**
* Merges app jank stats reported by components outside the platform to the current pending
* stats
*/
@@ -131,6 +146,9 @@ public class JankTracker {
getHandler().post(new Runnable() {
@Override
public void run() {
+ if (mJankDataProcessor == null) {
+ return;
+ }
mJankDataProcessor.mergeJankStats(appJankStats, mActivityName);
}
});
@@ -192,8 +210,7 @@ public class JankTracker {
public void enableAppJankTracking() {
// Add the activity as a state, this will ensure we track frames to the activity without the
// need for a decorated widget to be used.
- // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
- mStateTracker.putState("NONE", mActivityName, "NONE");
+ addActivityToStateTracking();
mTrackingEnabled = true;
registerForJankData();
}
@@ -203,27 +220,33 @@ public class JankTracker {
*/
public void disableAppJankTracking() {
mTrackingEnabled = false;
- // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
- mStateTracker.removeState("NONE", mActivityName, "NONE");
+ removeActivityFromStateTracking();
unregisterForJankData();
}
/**
- * Retrieve all pending widget states, this is intended for testing purposes only.
+ * Retrieve all pending widget states, this is intended for testing purposes only. If
+ * this is called before StateTracker has been created the method will just return without
+ * copying any data to the stateDataList parameter.
*
* @param stateDataList the ArrayList that will be populated with the pending states.
*/
@VisibleForTesting
public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) {
+ if (mStateTracker == null) return;
mStateTracker.retrieveAllStates(stateDataList);
}
/**
* Retrieve all pending jank stats before they are logged, this is intended for testing
- * purposes only.
+ * purposes only. If this method is called before JankDataProcessor is created it will return
+ * an empty HashMap.
*/
@VisibleForTesting
public HashMap<String, JankDataProcessor.PendingJankStat> getPendingJankStats() {
+ if (mJankDataProcessor == null) {
+ return new HashMap<>();
+ }
return mJankDataProcessor.getPendingJankStats();
}
@@ -233,8 +256,10 @@ public class JankTracker {
*/
@VisibleForTesting
public void forceListenerRegistration() {
+ addActivityToStateTracking();
mSurfaceControl = mDecorView.getRootSurfaceControl();
registerJankDataListener();
+ mListenersRegistered = true;
}
private void unregisterForJankData() {
@@ -270,6 +295,10 @@ public class JankTracker {
*/
@VisibleForTesting
public boolean shouldTrack() {
+ if (DEBUG) {
+ Log.d(DEBUG_KEY, String.format("mTrackingEnabled: %s | mListenersRegistered: %s",
+ mTrackingEnabled, mListenersRegistered));
+ }
return mTrackingEnabled && mListenersRegistered;
}
@@ -313,4 +342,36 @@ public class JankTracker {
}
return mHandler;
}
+
+ private void addActivityToStateTracking() {
+ if (mStateTracker == null) return;
+
+ mStateTracker.putState(AppJankStats.WIDGET_CATEGORY_UNSPECIFIED, mActivityName,
+ AppJankStats.WIDGET_STATE_UNSPECIFIED);
+ }
+
+ private void removeActivityFromStateTracking() {
+ if (mStateTracker == null) return;
+
+ mStateTracker.removeState(AppJankStats.WIDGET_CATEGORY_UNSPECIFIED, mActivityName,
+ AppJankStats.WIDGET_STATE_UNSPECIFIED);
+ }
+
+ private void initializeJankTrackingComponents() {
+ ViewRootImpl viewRoot = mDecorView.getViewRootImpl();
+ if (viewRoot == null || viewRoot.getChoreographer() == null) {
+ return;
+ }
+
+ if (mStateTracker == null) {
+ mStateTracker = new StateTracker(viewRoot.getChoreographer());
+ }
+
+ if (mJankDataProcessor == null) {
+ mJankDataProcessor = new JankDataProcessor(mStateTracker);
+ }
+
+ addActivityToStateTracking();
+ registerForJankData();
+ }
}
diff --git a/core/java/android/app/jank/TEST_MAPPING b/core/java/android/app/jank/TEST_MAPPING
new file mode 100644
index 000000000000..271eb4332f79
--- /dev/null
+++ b/core/java/android/app/jank/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "postsubmit": [
+ {
+ "name": "CoreAppJankTestCases"
+ },
+ {
+ "name": "CtsAppJankTestCases"
+ }
+ ]
+} \ No newline at end of file
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
index a62df1b3cbf7..de98b88d2aca 100644
--- a/core/java/android/app/jank/flags.aconfig
+++ b/core/java/android/app/jank/flags.aconfig
@@ -14,4 +14,14 @@ flag {
namespace: "system_performance"
description: "Controls whether the system will log frame metrics related to app jank"
bug: "366265225"
+}
+
+flag {
+ name: "viewroot_choreographer"
+ namespace: "system_performance"
+ description: "when enabled janktracker will get the instance of choreographer from viewrootimpl"
+ bug: "377960907"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 8e6b88c66408..5891bddfbbe6 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -63,6 +63,16 @@ flag {
}
flag {
+ name: "modes_ui_dnd_tile"
+ namespace: "systemui"
+ description: "Shows a dedicated tile for the DND mode; dependent on modes_ui"
+ bug: "401217520"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "modes_hsum"
namespace: "systemui"
description: "Fixes for modes (and DND/Zen in general) with HSUM or secondary users"
@@ -255,6 +265,26 @@ flag {
}
flag {
+ name: "redaction_on_lockscreen_metrics"
+ namespace: "systemui"
+ description: "enables metrics when redacting notifications on the lockscreen"
+ bug: "343631648"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "expanding_public_view"
+ namespace: "systemui"
+ description: "enables user expanding the public view of a notification"
+ bug: "398853084"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "api_rich_ongoing"
is_exported: true
namespace: "systemui"
@@ -337,6 +367,7 @@ flag {
namespace: "systemui"
description: "Allows the NAS to summarize notifications"
bug: "390417189"
+ is_exported: true
}
flag {
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 8217ca1953ab..172ed2358a5d 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -97,6 +97,8 @@ public class SupervisionManager {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS)
@RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
@Nullable
public Intent createConfirmSupervisionCredentialsIntent() {
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 0085e4f42397..4fb3982c3754 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -150,3 +150,11 @@ flag {
description: "Settings override for virtual devices"
bug: "371801645"
}
+
+flag {
+ namespace: "virtual_devices"
+ name: "viewconfiguration_apis"
+ description: "APIs for settings ViewConfiguration attributes on virtual devices"
+ bug: "370720522"
+ is_exported: true
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 55d78f9b8c48..cc288b1f5601 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -744,15 +744,22 @@ public abstract class Context {
*/
public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
+ /**
+ * Flag for {@link #bindService} that allows the bound app to be frozen if it is eligible.
+ *
+ * @hide
+ */
+ public static final long BIND_ALLOW_FREEZE = 0x4_0000_0000L;
/**
* These bind flags reduce the strength of the binding such that we shouldn't
* consider it as pulling the process up to the level of the one that is bound to it.
* @hide
*/
- public static final int BIND_REDUCTION_FLAGS =
+ public static final long BIND_REDUCTION_FLAGS =
Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_WAIVE_PRIORITY
- | Context.BIND_NOT_PERCEPTIBLE | Context.BIND_NOT_VISIBLE;
+ | Context.BIND_NOT_PERCEPTIBLE | Context.BIND_NOT_VISIBLE
+ | Context.BIND_ALLOW_FREEZE;
/** @hide */
@IntDef(flag = true, prefix = { "RECEIVER_VISIBLE" }, value = {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 038756148a32..bb62ac321202 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12426,6 +12426,8 @@ public class Intent implements Parcelable, Cloneable {
}
private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
+ // if forceUnparcel is false, do not unparcel the mExtras bundle.
+ // forceUnparcel will only be true when this method is called from system server.
if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
for (String key : mExtras.keySet()) {
@@ -12440,6 +12442,7 @@ public class Intent implements Parcelable, Cloneable {
value = mExtras.get(key);
} else {
value = null;
+ removeExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
}
} catch (BadParcelableException e) {
// This may still happen if the keys are collected on the system server side, in
@@ -12459,6 +12462,13 @@ public class Intent implements Parcelable, Cloneable {
}
}
+ // if there is no extras in the bundle, we also mark the intent as keys are collected.
+ // isDefinitelyEmpty() will not unparceled the mExtras. This is the best we can do without
+ // unparceling the extra bundle.
+ if (mExtras == null || mExtras.isDefinitelyEmpty()) {
+ addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
+ }
+
if (mClipData != null) {
for (int i = 0; i < mClipData.getItemCount(); i++) {
Intent intent = mClipData.getItemAt(i).mIntent;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 74da62c85ed2..ded35b23608d 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -31,13 +31,13 @@ import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseArrayMap;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -98,15 +98,16 @@ public abstract class RegisteredServicesCache<V> {
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
- @GuardedBy("mServiceInfoCaches")
- private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+ @GuardedBy("mUserIdToServiceInfoCaches")
+ private final SparseArrayMap<ComponentName, ServiceInfo<V>> mUserIdToServiceInfoCaches =
+ new SparseArrayMap<>();
private final Handler mBackgroundHandler;
private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
public void run() {
- synchronized (mServiceInfoCaches) {
- mServiceInfoCaches.clear();
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.clear();
}
}
};
@@ -166,7 +167,15 @@ public abstract class RegisteredServicesCache<V> {
@UnsupportedAppUsage
public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
- mContext = context;
+ this(new Injector<V>(context), interfaceName, metaDataName, attributeName,
+ serializerAndParser);
+ }
+
+ /** Provides the basic functionality for unit tests. */
+ @VisibleForTesting
+ public RegisteredServicesCache(Injector<V> injector, String interfaceName, String metaDataName,
+ String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
+ mContext = injector.getContext();
mInterfaceName = interfaceName;
mMetaDataName = metaDataName;
mAttributesName = attributeName;
@@ -184,7 +193,7 @@ public abstract class RegisteredServicesCache<V> {
if (isCore) {
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mBackgroundHandler = BackgroundThread.getHandler();
+ mBackgroundHandler = injector.getBackgroundHandler();
mContext.registerReceiverAsUser(
mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
@@ -529,8 +538,8 @@ public abstract class RegisteredServicesCache<V> {
Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
}
if (lastUpdateTime >= 0) {
- ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
- lastUpdateTime);
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(userId,
+ componentName, lastUpdateTime);
if (serviceInfo != null) {
serviceInfos.add(serviceInfo);
continue;
@@ -545,8 +554,8 @@ public abstract class RegisteredServicesCache<V> {
}
serviceInfos.add(info);
if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServiceInfoCaches) {
- mServiceInfoCaches.put(componentName, info);
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.add(userId, componentName, info);
}
}
} catch (XmlPullParserException | IOException e) {
@@ -555,8 +564,8 @@ public abstract class RegisteredServicesCache<V> {
}
if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServiceInfoCaches) {
- if (!mServiceInfoCaches.isEmpty()) {
+ synchronized (mUserIdToServiceInfoCaches) {
+ if (mUserIdToServiceInfoCaches.numMaps() > 0) {
mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
@@ -865,6 +874,11 @@ public abstract class RegisteredServicesCache<V> {
synchronized (mServicesLock) {
mUserServices.remove(userId);
}
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.delete(userId);
+ }
+ }
}
@VisibleForTesting
@@ -908,14 +922,35 @@ public abstract class RegisteredServicesCache<V> {
mContext.unregisterReceiver(mUserRemovedReceiver);
}
- private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
- long lastUpdateTime) {
- synchronized (mServiceInfoCaches) {
- ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+ private ServiceInfo<V> getServiceInfoFromServiceCache(int userId,
+ @NonNull ComponentName componentName, long lastUpdateTime) {
+ synchronized (mUserIdToServiceInfoCaches) {
+ ServiceInfo<V> serviceCache = mUserIdToServiceInfoCaches.get(userId, componentName);
if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
return serviceCache;
}
return null;
}
}
+
+ /**
+ * Point of injection for test dependencies.
+ * @param <V> The type of the value.
+ */
+ @VisibleForTesting
+ public static class Injector<V> {
+ private final Context mContext;
+
+ public Injector(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Handler getBackgroundHandler() {
+ return BackgroundThread.getHandler();
+ }
+ }
}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 23f1ff8926df..92ae15a296b7 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -136,6 +136,28 @@
]
},
{
+ "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJMultiUsersTestCases",
"options":[
{
@@ -261,6 +283,28 @@
]
},
{
+ "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJMultiUsersTestCases",
"options":[
{
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 582a1a9442ce..53203eba4020 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -410,6 +410,11 @@ public class UserInfo implements Parcelable {
return UserManager.isUserTypePrivateProfile(userType);
}
+ @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE)
+ public boolean isSupervisingProfile() {
+ return UserManager.isUserTypeSupervisingProfile(userType);
+ }
+
/** See {@link #FLAG_DISABLED}*/
@UnsupportedAppUsage
public boolean isEnabled() {
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5c904c15e706..7f57f5dbf0ab 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -617,8 +617,8 @@ flag {
}
flag {
- namespace: "multi_user"
name: "logout_user_api"
+ namespace: "multiuser"
description: "Add API to logout user"
bug: "350045389"
}
@@ -629,3 +629,10 @@ flag {
description: "Enable moving content into the Private Space"
bug: "360066001"
}
+
+flag {
+ name: "allow_supervising_profile"
+ namespace: "supervision"
+ description: "Enables support for new supervising user type"
+ bug: "389712089"
+}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 75c7e267d477..040dcfdeb998 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -138,7 +138,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private static native long nativeOpen(String path, int openFlags, String label,
boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
int lookasideSlotCount);
- private static native void nativeClose(long connectionPtr);
+ private static native void nativeClose(long connectionPtr, boolean fast);
private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
String name, UnaryOperator<String> function);
private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
@@ -300,7 +300,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
- nativeClose(mConnectionPtr);
+ nativeClose(mConnectionPtr, finalized && Flags.noCheckpointOnFinalize());
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 1d17a51f3653..9f4f1a16178b 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -5,7 +5,7 @@ flag {
name: "oneway_finalizer_close_fixed"
namespace: "system_performance"
is_fixed_read_only: true
- description: "Make BuildCursorNative.close oneway if in the the finalizer"
+ description: "Make BuildCursorNative.close oneway if in the finalizer"
bug: "368221351"
}
@@ -26,3 +26,10 @@ flag {
description: "Make SQLiteOpenHelper thread-safe"
bug: "335904370"
}
+
+flag {
+ name: "no_checkpoint_on_finalize"
+ namespace: "system_performance"
+ description: "Do not checkpoint WAL if closing in the finalizer"
+ bug: "397982577"
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index bcb7ebfb286f..6e9dcf5a83a1 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1714,7 +1714,7 @@ public final class CameraManager {
final TaskInfo taskInfo = appTask.getTaskInfo();
final int freeformCameraCompatMode = taskInfo.appCompatTaskInfo
.cameraCompatTaskInfo.freeformCameraCompatMode;
- if (freeformCameraCompatMode != 0
+ if (isInCameraCompatMode(freeformCameraCompatMode)
&& taskInfo.topActivity != null
&& taskInfo.topActivity.getPackageName().equals(packageName)) {
// WindowManager has requested rotation override.
@@ -1741,6 +1741,12 @@ public final class CameraManager {
: ICameraService.ROTATION_OVERRIDE_NONE;
}
+ private static boolean isInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int
+ freeformCameraCompatMode) {
+ return (freeformCameraCompatMode != CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED)
+ && (freeformCameraCompatMode != CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
+ }
+
private static int getRotationOverrideForCompatFreeform(
@CameraCompatTaskInfo.FreeformCameraCompatMode int freeformCameraCompatMode) {
// Only rotate-and-crop if the app and device orientations do not match.
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
index fdde2057a1a0..de3bafb4bb56 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
@@ -75,7 +75,6 @@ public class MarshalQueryableParcelable<T extends Parcelable>
}
Parcel parcel = Parcel.obtain();
- byte[] parcelContents;
try {
value.writeToParcel(parcel, /*flags*/0);
@@ -85,17 +84,15 @@ public class MarshalQueryableParcelable<T extends Parcelable>
"Parcelable " + value + " must not have file descriptors");
}
- parcelContents = parcel.marshall();
+ final int position = buffer.position();
+ parcel.marshall(buffer);
+ if (buffer.position() == position) {
+ throw new AssertionError("No data marshaled for " + value);
+ }
}
finally {
parcel.recycle();
}
-
- if (parcelContents.length == 0) {
- throw new AssertionError("No data marshaled for " + value);
- }
-
- buffer.put(parcelContents);
}
@Override
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 92a56fc575e3..8216130e3731 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -433,8 +433,9 @@ public final class DisplayManager {
public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
/**
- * Virtual display flag: Indicates that the display should support system decorations. Virtual
- * displays without this flag shouldn't show home, navigation bar or wallpaper.
+ * Virtual display flag: Indicates that the display supports and should always show system
+ * decorations. Virtual displays without this flag shouldn't show home, navigation bar or
+ * wallpaper.
* <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p>
*
* @see #createVirtualDisplay
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 343e4b5668c0..0c8a9ed56ae8 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -476,6 +476,16 @@ public abstract class DisplayManagerInternal {
public abstract boolean isDisplayReadyForMirroring(int displayId);
/**
+ * Called by {@link com.android.server.wm.WindowManagerService} to notify whether a display
+ * should be in the topology.
+ * @param displayId The logical ID of the display
+ * @param inTopology Whether the display should be in the topology. This being true does not
+ * guarantee that the display will be in the topology - Display Manager might
+ * also check other parameters.
+ */
+ public abstract void onDisplayBelongToTopologyChanged(int displayId, boolean inTopology);
+
+ /**
* Called by {@link com.android.server.display.DisplayBackupHelper} when backup files were
* restored and are ready to be reloaded.
*/
diff --git a/core/java/android/hardware/input/IKeyGestureHandler.aidl b/core/java/android/hardware/input/IKeyGestureHandler.aidl
index 509b9482154e..4da991ee85b1 100644
--- a/core/java/android/hardware/input/IKeyGestureHandler.aidl
+++ b/core/java/android/hardware/input/IKeyGestureHandler.aidl
@@ -28,15 +28,4 @@ interface IKeyGestureHandler {
* to that gesture.
*/
boolean handleKeyGesture(in AidlKeyGestureEvent event, in IBinder focusedToken);
-
- /**
- * Called to know if a particular gesture type is supported by the handler.
- *
- * TODO(b/358569822): Remove this call to reduce the binder calls to single call for
- * handleKeyGesture. For this we need to remove dependency of multi-key gestures to identify if
- * a key gesture is supported on first relevant key down.
- * Also, for now we prioritize handlers in the system server process above external handlers to
- * reduce IPC binder calls.
- */
- boolean isKeyGestureSupported(int gestureType);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 49db54d81e65..d6419afb2a5a 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1758,13 +1758,6 @@ public final class InputManager {
*/
boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
@Nullable IBinder focusedToken);
-
- /**
- * Called to identify if a particular gesture is of interest to a handler.
- *
- * NOTE: If no active handler supports certain gestures, the gestures will not be captured.
- */
- boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType);
}
/** @hide */
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index a9a45ae45ec3..c4b4831ba76e 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1193,23 +1193,6 @@ public final class InputManagerGlobal {
}
return false;
}
-
- @Override
- public boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) {
- synchronized (mKeyGestureEventHandlerLock) {
- if (mKeyGestureEventHandlers == null) {
- return false;
- }
- final int numHandlers = mKeyGestureEventHandlers.size();
- for (int i = 0; i < numHandlers; i++) {
- KeyGestureEventHandler handler = mKeyGestureEventHandlers.get(i);
- if (handler.isKeyGestureSupported(gestureType)) {
- return true;
- }
- }
- }
- return false;
- }
}
/**
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 1a712d2b3f31..9dd1fed4a85a 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -108,7 +108,8 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD = 55;
public static final int KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD = 56;
public static final int KEY_GESTURE_TYPE_GLOBAL_ACTIONS = 57;
- public static final int KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD = 58;
+ @Deprecated
+ public static final int DEPRECATED_KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD = 58;
public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59;
public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60;
public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61;
@@ -200,7 +201,6 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD,
KEY_GESTURE_TYPE_GLOBAL_ACTIONS,
- KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
@@ -777,8 +777,6 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD";
case KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
return "KEY_GESTURE_TYPE_GLOBAL_ACTIONS";
- case KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
- return "KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD";
case KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
return "KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT";
case KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt b/core/java/android/hardware/usb/IUsbManagerInternal.aidl
index 626a68f6a59d..32479d449f24 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt
+++ b/core/java/android/hardware/usb/IUsbManagerInternal.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.display.data
+package android.hardware.usb;
-sealed interface DisplayEvent {
- val displayId: Int
- data class Added(override val displayId: Int) : DisplayEvent
- data class Removed(override val displayId: Int) : DisplayEvent
- data class Changed(override val displayId: Int) : DisplayEvent
+import android.hardware.usb.IUsbOperationInternal;
+
+/** @hide */
+interface IUsbManagerInternal {
+
+ /* Disable/enable USB data on a port for System Service callers. */
+ boolean enableUsbDataSignal(boolean enable, int disableReason);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7b47efd47008..894b068b1528 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -527,22 +527,13 @@ public class InputMethodService extends AbstractInputMethodService {
public static final int IME_ACTIVE = 1 << 0;
/**
- * The IME is perceptibly visible to the user.
+ * The IME is visible.
*
* @hide
*/
public static final int IME_VISIBLE = 1 << 1;
/**
- * The IME is visible, but not yet perceptible to the user (e.g. fading in)
- * by {@link android.view.WindowInsetsController}.
- *
- * @see InputMethodManager#reportPerceptible
- * @hide
- */
- public static final int IME_VISIBLE_IMPERCEPTIBLE = 1 << 2;
-
- /**
* The IME window visibility state.
*
* @hide
@@ -550,7 +541,6 @@ public class InputMethodService extends AbstractInputMethodService {
@IntDef(flag = true, prefix = { "IME_" }, value = {
IME_ACTIVE,
IME_VISIBLE,
- IME_VISIBLE_IMPERCEPTIBLE,
})
public @interface ImeWindowVisibility {}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index ce1717b60bfd..c3ec96d17437 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -76,8 +76,13 @@ public final class MessageQueue {
@SuppressWarnings("unused")
private long mPtr; // used by native code
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(
+ maxTargetSdk = Build.VERSION_CODES.BAKLAVA,
+ publicAlternatives =
+ "To manipulate the queue in Instrumentation tests, use {@link"
+ + " android.os.TestLooperManager}")
Message mMessages;
+
private Message mLast;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
@@ -139,9 +144,18 @@ public final class MessageQueue {
return;
}
- if (RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
- sIsProcessAllowedToUseConcurrent = false;
- return;
+ if (Flags.forceConcurrentMessageQueue()) {
+ // b/379472827: Robolectric tests use reflection to access MessageQueue.mMessages.
+ // This is a hack to allow Robolectric tests to use the legacy implementation.
+ try {
+ Class.forName("org.robolectric.Robolectric");
+ } catch (ClassNotFoundException e) {
+ // This is not a Robolectric test.
+ sIsProcessAllowedToUseConcurrent = true;
+ return;
+ }
+ // This is a Robolectric test.
+ // Continue to the following checks.
}
final String processName = Process.myProcessName();
@@ -995,7 +1009,11 @@ public final class MessageQueue {
}
}
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(
+ maxTargetSdk = Build.VERSION_CODES.BAKLAVA,
+ publicAlternatives =
+ "To manipulate the queue in Instrumentation tests, use {@link"
+ + " android.os.TestLooperManager}")
Message next() {
if (mUseConcurrent) {
return nextConcurrent();
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
index 3aad0fd7b82f..70a17ab00235 100644
--- a/core/java/android/os/ExternalVibration.java
+++ b/core/java/android/os/ExternalVibration.java
@@ -87,8 +87,12 @@ public class ExternalVibration implements Parcelable {
int capturePreset = in.readInt();
int flags = in.readInt();
AudioAttributes.Builder builder = new AudioAttributes.Builder();
- return builder.setUsage(usage)
- .setContentType(contentType)
+ if (AudioAttributes.isSystemUsage(usage)) {
+ builder.setSystemUsage(usage);
+ } else {
+ builder.setUsage(usage);
+ }
+ return builder.setContentType(contentType)
.setCapturePreset(capturePreset)
.setFlags(flags)
.build();
@@ -196,7 +200,9 @@ public class ExternalVibration implements Parcelable {
}
private static void writeAudioAttributes(AudioAttributes attrs, Parcel out) {
- out.writeInt(attrs.getUsage());
+ // Since we allow audio system usages, must use getSystemUsage() instead of getUsage() for
+ // all usages.
+ out.writeInt(attrs.getSystemUsage());
out.writeInt(attrs.getContentType());
out.writeInt(attrs.getCapturePreset());
out.writeInt(attrs.getAllFlags());
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index 132bdd1e56b8..1cf57de08d5f 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -786,8 +786,8 @@ public final class MessageQueue {
}
/**
- * Get the timestamp of the next executable message in our priority queue.
- * Returns null if there are no messages ready for delivery.
+ * Get the timestamp of the next message in our priority queue.
+ * Returns null if there are no messages in the queue.
*
* Caller must ensure that this doesn't race 'next' from the Looper thread.
*/
@@ -799,8 +799,8 @@ public final class MessageQueue {
}
/**
- * Return the next executable message in our priority queue.
- * Returns null if there are no messages ready for delivery
+ * Return the next message in our priority queue.
+ * Returns null if there are no messages in the queue.
*
* Caller must ensure that this doesn't race 'next' from the Looper thread.
*/
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 49d3f06eb80f..6cb49b3ea166 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -52,6 +52,8 @@ import com.android.internal.util.ArrayUtils;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.nio.BufferOverflowException;
+import java.nio.ReadOnlyBufferException;
import libcore.util.SneakyThrow;
import java.io.ByteArrayInputStream;
@@ -62,6 +64,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
+import java.nio.ByteBuffer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
@@ -457,8 +460,15 @@ public final class Parcel {
private static native void nativeDestroy(long nativePtr);
private static native byte[] nativeMarshall(long nativePtr);
+ private static native int nativeMarshallArray(
+ long nativePtr, byte[] data, int offset, int length);
+ private static native int nativeMarshallBuffer(
+ long nativePtr, ByteBuffer buffer, int offset, int length);
private static native void nativeUnmarshall(
long nativePtr, byte[] data, int offset, int length);
+ private static native void nativeUnmarshallBuffer(
+ long nativePtr, ByteBuffer buffer, int offset, int length);
+
private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
private static native boolean nativeCompareDataInRange(
long ptrA, int offsetA, long ptrB, int offsetB, int length);
@@ -814,12 +824,80 @@ public final class Parcel {
}
/**
+ * Writes the raw bytes of the parcel to a buffer.
+ *
+ * <p class="note">The data you retrieve here <strong>must not</strong>
+ * be placed in any kind of persistent storage (on local disk, across
+ * a network, etc). For that, you should use standard serialization
+ * or another kind of general serialization mechanism. The Parcel
+ * marshalled representation is highly optimized for local IPC, and as
+ * such does not attempt to maintain compatibility with data created
+ * in different versions of the platform.
+ *
+ * @param buffer The ByteBuffer to write the data to.
+ * @throws ReadOnlyBufferException if the buffer is read-only.
+ * @throws BufferOverflowException if the buffer is too small.
+ *
+ * @hide
+ */
+ public final void marshall(@NonNull ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new NullPointerException();
+ }
+ if (buffer.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
+
+ final int position = buffer.position();
+ final int remaining = buffer.remaining();
+
+ int marshalledSize = 0;
+ if (buffer.isDirect()) {
+ marshalledSize = nativeMarshallBuffer(mNativePtr, buffer, position, remaining);
+ } else if (buffer.hasArray()) {
+ marshalledSize = nativeMarshallArray(
+ mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining);
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ buffer.position(position + marshalledSize);
+ }
+
+ /**
* Fills the raw bytes of this Parcel with the supplied data.
*/
public final void unmarshall(@NonNull byte[] data, int offset, int length) {
nativeUnmarshall(mNativePtr, data, offset, length);
}
+ /**
+ * Fills the raw bytes of this Parcel with data from the supplied buffer.
+ *
+ * @param buffer will read buffer.remaining() bytes from the buffer.
+ *
+ * @hide
+ */
+ public final void unmarshall(@NonNull ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new NullPointerException();
+ }
+
+ final int position = buffer.position();
+ final int remaining = buffer.remaining();
+
+ if (buffer.isDirect()) {
+ nativeUnmarshallBuffer(mNativePtr, buffer, position, remaining);
+ } else if (buffer.hasArray()) {
+ nativeUnmarshall(
+ mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining);
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ buffer.position(position + remaining);
+ }
+
public final void appendFrom(Parcel parcel, int offset, int length) {
nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0c5d9e97a77d..b68b9a7af5e2 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1347,6 +1347,7 @@ public class Process {
* Return the name of this process. By default, the process name is the same as the app's
* package name, but this can be changed using {@code android:process}.
*/
+ @RavenwoodReplace
@NonNull
public static String myProcessName() {
// Note this could be different from the actual process name if someone changes the
@@ -1355,6 +1356,12 @@ public class Process {
return sArgV0;
}
+ /** @hide */
+ @NonNull
+ public static String myProcessName$ravenwood() {
+ return "ravenwood";
+ }
+
/**
* Kill the process with the given PID.
* Note that, though this API allows us to request to
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 1a54f4df58fb..204e3444c547 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -96,8 +96,8 @@ public class TestLooperManager {
}
/**
- * Retrieves and removes the next message that should be executed by this queue.
- * If the queue is empty or no messages are deliverable, returns null.
+ * Retrieves and removes the next message in this queue.
+ * If the queue is empty, returns null.
* This method never blocks.
*
* <p>Callers should always call {@link #recycle(Message)} on the message when all interactions
@@ -112,9 +112,9 @@ public class TestLooperManager {
}
/**
- * Retrieves, but does not remove, the values of {@link Message#when} of next message that
- * should be executed by this queue.
- * If the queue is empty or no messages are deliverable, returns null.
+ * Retrieves, but does not remove, the values of {@link Message#when} of next message in the
+ * queue.
+ * If the queue is empty, returns null.
* This method never blocks.
*/
@FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 767019d97758..c01c3cdc7ace 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -209,6 +209,23 @@ public class UserManager {
public static final String USER_TYPE_PROFILE_COMMUNAL = "android.os.usertype.profile.COMMUNAL";
/**
+ * User type representing a user who manages supervision on the device.
+ * When any full user on the device is supervised, the credentials for this profile will be
+ * required in order to perform certain actions for that user (i.e. those controlled by
+ * {@link android.app.supervision.SupervisionManager} or the
+ * {@link android.app.role.RoleManager#ROLE_SYSTEM_SUPERVISION supervision role holder}).
+ * There can only be one supervising profile per device, and the credentials set for that
+ * profile will be used to authorize actions for any supervised user on the device. This is
+ * distinct from a managed profile in that it functions only to authorize certain supervised
+ * actions; it does not represent the user to which restriction or management is applied.
+ * @hide
+ */
+ @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE)
+ @SystemApi
+ public static final String USER_TYPE_PROFILE_SUPERVISING =
+ "android.os.usertype.profile.SUPERVISING";
+
+ /**
* User type representing a {@link UserHandle#USER_SYSTEM system} user that is <b>not</b> a
* human user.
* This type of user cannot be created; it can only pre-exist on first boot.
@@ -3226,6 +3243,18 @@ public class UserManager {
}
/**
+ * Returns whether the user type is a
+ * {@link UserManager#USER_TYPE_PROFILE_SUPERVISING supervising profile}.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE)
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static boolean isUserTypeSupervisingProfile(@Nullable String userType) {
+ return USER_TYPE_PROFILE_SUPERVISING.equals(userType);
+ }
+
+ /**
* @hide
* @deprecated Use {@link #isRestrictedProfile()}
*/
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d3c677bf8af2..86acb2b21cfa 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -211,6 +211,14 @@ flag {
}
flag {
+ name: "force_concurrent_message_queue"
+ namespace: "system_performance"
+ is_exported: true
+ description: "Whether MessageQueue uses the new concurrent implementation"
+ bug: "336880969"
+}
+
+flag {
name: "get_private_space_settings"
namespace: "profile_experiences"
description: "Guards a new Private Profile API in LauncherApps"
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index a8a22f675e08..b82f278ef7d5 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -216,7 +216,7 @@ public class SystemHealthManager {
/**
* Gets the maximum number of TIDs this device supports for getting CPU headroom.
* <p>
- * See {@link CpuHeadroomParams#setTids(int...)}.
+ * See {@link CpuHeadroomParams.Builder#setTids(int...)}.
*
* @return the maximum size of TIDs supported
* @throws UnsupportedOperationException if the CPU headroom API is unsupported.
@@ -288,9 +288,7 @@ public class SystemHealthManager {
/**
* Gets the range of the calculation window size for CPU headroom.
* <p>
- * In API version 36, the range will be a superset of [50, 10000].
- * <p>
- * See {@link CpuHeadroomParams#setCalculationWindowMillis(int)}.
+ * See {@link CpuHeadroomParams.Builder#setCalculationWindowMillis(int)}.
*
* @return the range of the calculation window size supported in milliseconds.
* @throws UnsupportedOperationException if the CPU headroom API is unsupported.
@@ -310,9 +308,7 @@ public class SystemHealthManager {
/**
* Gets the range of the calculation window size for GPU headroom.
* <p>
- * In API version 36, the range will be a superset of [50, 10000].
- * <p>
- * See {@link GpuHeadroomParams#setCalculationWindowMillis(int)}.
+ * See {@link GpuHeadroomParams.Builder#setCalculationWindowMillis(int)}.
*
* @return the range of the calculation window size supported in milliseconds.
* @throws UnsupportedOperationException if the GPU headroom API is unsupported.
diff --git a/core/java/android/os/instrumentation/MethodDescriptorParser.java b/core/java/android/os/instrumentation/MethodDescriptorParser.java
index 57fc44ff623e..3264c041213b 100644
--- a/core/java/android/os/instrumentation/MethodDescriptorParser.java
+++ b/core/java/android/os/instrumentation/MethodDescriptorParser.java
@@ -18,7 +18,7 @@ package android.os.instrumentation;
import android.annotation.NonNull;
-import java.lang.reflect.Method;
+import java.lang.reflect.Executable;
/**
* A utility class for dynamic instrumentation / uprobestats.
@@ -28,9 +28,9 @@ import java.lang.reflect.Method;
public final class MethodDescriptorParser {
/**
- * Parses a {@link MethodDescriptor} (in string representation) into a {@link Method}.
+ * Parses a {@link MethodDescriptor} (in string representation) into a {@link Executable}.
*/
- public static Method parseMethodDescriptor(ClassLoader classLoader,
+ public static Executable parseMethodDescriptor(ClassLoader classLoader,
@NonNull MethodDescriptor descriptor) {
try {
Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName);
@@ -72,6 +72,10 @@ public final class MethodDescriptorParser {
}
}
+ if (com.android.art.flags.Flags.executableMethodFileOffsetsV2()
+ && descriptor.methodName.equals("<init>")) {
+ return javaClass.getDeclaredConstructor(parameters);
+ }
return javaClass.getDeclaredMethod(descriptor.methodName, parameters);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new IllegalArgumentException(
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 414f27498414..176c0c8ab966 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -165,3 +165,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "haptics"
+ name: "remove_hidl_support"
+ description: "Remove framework code to support HIDL vibrator HALs."
+ bug: "308452413"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 561a2c96e6a7..0433c76fbbf4 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1907,8 +1907,9 @@ public final class PermissionManager {
@Context.PermissionRequestState
public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission,
int deviceId) {
+ int actualDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permission);
return sPermissionRequestStateCache.query(
- new PermissionRequestStateQuery(packageName, permission, deviceId));
+ new PermissionRequestStateQuery(packageName, permission, actualDeviceId));
}
/**
@@ -2035,7 +2036,8 @@ public final class PermissionManager {
*/
public int checkPackageNamePermission(String permName, String pkgName,
int deviceId, @UserIdInt int userId) {
- String persistentDeviceId = getPersistentDeviceId(deviceId);
+ int actualDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permName);
+ String persistentDeviceId = getPersistentDeviceId(actualDeviceId);
return sPackageNamePermissionCache.query(
new PackageNamePermissionQuery(permName, pkgName, persistentDeviceId, userId));
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index ca24c0c6c376..34272b17cf54 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -71,6 +71,19 @@ flag {
}
flag {
+ name: "unknown_call_setting_blocked_logging_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "enable the metrics when blocking certain app installs during an unknown call"
+ bug: "364535720"
+
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "op_enable_mobile_data_by_user"
is_exported: true
namespace: "permissions"
@@ -358,7 +371,25 @@ flag {
is_fixed_read_only: true
is_exported: true
namespace: "permissions"
- description: "Enables SQlite for recording discrete and historical AppOp accesses"
+ description: "Enables SQlite for recording individual/discrete AppOp accesses"
+ bug: "377584611"
+}
+
+flag {
+ name: "enable_all_sqlite_appops_accesses"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables SQlite for storing aggregated & individual/discrete AppOp accesses"
+ bug: "377584611"
+}
+
+flag {
+ name: "record_all_runtime_appops_sqlite"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables recording of all runtime app ops into SQlite"
bug: "377584611"
}
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 6b813b0c04f6..96548513bf37 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -109,6 +109,7 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi
private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
private Drawable mDividerDrawable;
private boolean mDividerSpecified;
+ private boolean mDialogFitsSystemWindows = false;
/**
* Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}.
@@ -136,6 +137,18 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi
}
/**
+ * Used in {@link #onClick()} to override the {@link View#setFitsSystemWindows(boolean)} for
+ * the dialog that shows. This is set separately to limit the scope of this change to just
+ * the {@link PreferenceScreen} instances which have demonstrated an issue with edge to edge.
+ *
+ * @param dialogFitsSystemWindows value passed to {@link View#setFitsSystemWindows(boolean)}.
+ * @hide
+ */
+ public void setDialogFitsSystemWindows(boolean dialogFitsSystemWindows) {
+ mDialogFitsSystemWindows = dialogFitsSystemWindows;
+ }
+
+ /**
* Returns an adapter that can be attached to a {@link PreferenceActivity}
* or {@link PreferenceFragment} to show the preferences contained in this
* {@link PreferenceScreen}.
@@ -201,6 +214,11 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi
View childPrefScreen = inflater.inflate(mLayoutResId, null);
View titleView = childPrefScreen.findViewById(android.R.id.title);
mListView = (ListView) childPrefScreen.findViewById(android.R.id.list);
+ // Don't override any potential state that may exist on mListView. If it was already marked
+ // as "setFitsSystemWindows(true)" somewhere else don't change to "false" here.
+ if (mDialogFitsSystemWindows) {
+ mListView.setFitsSystemWindows(true);
+ }
if (mDividerSpecified) {
mListView.setDivider(mDividerDrawable);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1210790668de..85f38c984f5c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4184,7 +4184,6 @@ public final class Settings {
MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL);
MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME);
MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL);
- MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
MOVED_TO_SECURE.add(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT);
MOVED_TO_SECURE.add(Secure.WIFI_ON);
@@ -4223,6 +4222,7 @@ public final class Settings {
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS);
MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_MAX_DHCP_RETRY_COUNT);
+ MOVED_TO_SECURE_THEN_GLOBAL.add(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
// these are moving directly from system to global
MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON);
@@ -6484,6 +6484,14 @@ public final class Settings {
public static final String SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
/**
+ * Setting to enable CV (proprietary)
+ *
+ * @hide
+ */
+ public static final String CV_ENABLED =
+ "cv_enabled";
+
+ /**
* Integer property that specifes the color for screen flash notification as a
* packed 32-bit color.
*
@@ -8651,6 +8659,34 @@ public final class Settings {
public static final String DOCKED_CLOCK_FACE = "docked_clock_face";
/**
+ * Setting to indicate that content filters should be enabled on web browsers.
+ *
+ * <ul>
+ * <li>0 = Allow all sites
+ * <li>1 = Try to block explicit sites
+ * </ul>
+ *
+ * @hide
+ */
+ @Readable
+ public static final String BROWSER_CONTENT_FILTERS_ENABLED =
+ "browser_content_filters_enabled";
+
+ /**
+ * Setting to indicate that content filters should be enabled in web search engines.
+ *
+ * <ul>
+ * <li>0 = Off
+ * <li>1 = Filter
+ * </ul>
+ *
+ * @hide
+ */
+ @Readable
+ public static final String SEARCH_CONTENT_FILTERS_ENABLED =
+ "search_content_filters_enabled";
+
+ /**
* Set by the system to track if the user needs to see the call to action for
* the lockscreen notification policy.
* @hide
@@ -9465,24 +9501,6 @@ public final class Settings {
"reduce_bright_colors_persist_across_reboots";
/**
- * Setting that specifies whether Even Dimmer - a feature that allows the brightness
- * slider to go below what the display can conventionally do, should be enabled.
- *
- * @hide
- */
- public static final String EVEN_DIMMER_ACTIVATED =
- "even_dimmer_activated";
-
- /**
- * Setting that specifies which nits level Even Dimmer should allow the screen brightness
- * to go down to.
- *
- * @hide
- */
- public static final String EVEN_DIMMER_MIN_NITS =
- "even_dimmer_min_nits";
-
- /**
* Setting that holds EM_VALUE (proprietary)
*
* @hide
@@ -10601,9 +10619,63 @@ public final class Settings {
*
* @hide
*/
+ @TestApi
+ @Readable
+ @SuppressLint({"UnflaggedApi", "NoSettingsProvider"}) // @TestApi purely for CTS support.
public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled";
/**
+ * Indicates that glanceable hub should never be started automatically.
+ *
+ * @hide
+ */
+ public static final int GLANCEABLE_HUB_START_NEVER = 0;
+
+ /**
+ * Indicates that glanceable hub should be started when charging.
+ *
+ * @hide
+ */
+ public static final int GLANCEABLE_HUB_START_CHARGING = 1;
+
+ /**
+ * Indicates that glanceable hub should be started when charging and upright.
+ *
+ * @hide
+ */
+ public static final int GLANCEABLE_HUB_START_CHARGING_UPRIGHT = 2;
+
+ /**
+ * Indicates that glanceable hub should be started when docked.
+ *
+ * @hide
+ */
+ public static final int GLANCEABLE_HUB_START_DOCKED = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ GLANCEABLE_HUB_START_NEVER,
+ GLANCEABLE_HUB_START_CHARGING,
+ GLANCEABLE_HUB_START_CHARGING_UPRIGHT,
+ GLANCEABLE_HUB_START_DOCKED,
+ })
+ public @interface WhenToStartGlanceableHub {
+ }
+
+ /**
+ * Indicates when to start glanceable hub. Possible values are:
+ * 0: Never
+ * 1: While charging always
+ * 2: While upright and charging
+ * 3: While docked
+ *
+ * @hide
+ */
+ public static final String WHEN_TO_START_GLANCEABLE_HUB =
+ "when_to_start_glanceable_hub";
+
+ /**
* Whether home controls are enabled to be shown over the screensaver by the user.
*
* @hide
@@ -11147,6 +11219,12 @@ public final class Settings {
public static final String DOUBLE_TAP_TO_WAKE = "double_tap_to_wake";
/**
+ * Controls whether double tap to sleep is enabled.
+ * @hide
+ */
+ public static final String DOUBLE_TAP_TO_SLEEP = "double_tap_to_sleep";
+
+ /**
* The current assistant component. It could be a voice interaction service,
* or an activity that handles ACTION_ASSIST, or empty which means using the default
* handling.
@@ -12514,6 +12592,48 @@ public final class Settings {
"accessibility_magnification_always_on_enabled";
/**
+ * Controls how the magnification follows the cursor.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE =
+ "accessibility_magnification_cursor_following_mode";
+
+ /**
+ * Magnification cursor following mode value for the continuous mode.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS = 0;
+
+ /**
+ * Magnification cursor following mode value for the center mode.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER = 1;
+
+ /**
+ * Magnification cursor following mode value for the edge mode.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE = 2;
+
+ /**
+ * Different cursor following settings that can be used as values with
+ * {@link #ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_" },
+ value = {
+ ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS,
+ ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER,
+ ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE})
+ public @interface AccessibilityMagnificationCursorFollowingMode {}
+
+ /**
* Whether the following typing focus feature for magnification is enabled.
* @hide
*/
@@ -12710,6 +12830,22 @@ public final class Settings {
public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled";
/**
+ * Whether the Adaptive wifi scorer switch is enabled.
+ *
+ * @hide
+ */
+ public static final String ADAPTIVE_CONNECTIVITY_WIFI_ENABLED =
+ "adaptive_connectivity_wifi_enabled";
+
+ /**
+ * Whether the Adaptive 5G PM switch is enabled.
+ *
+ * @hide
+ */
+ public static final String ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED =
+ "adaptive_connectivity_mobile_network_enabled";
+
+ /**
* Controls the 'Sunlight boost' toggle in wearable devices (high brightness mode).
*
* Valid values for this key are: '0' (disabled) or '1' (enabled).
@@ -15386,7 +15522,8 @@ public final class Settings {
* <ul>
* <li><pre>secure</pre>: creates a secure display</li>
* <li><pre>own_content_only</pre>: only shows this display's own content</li>
- * <li><pre>should_show_system_decorations</pre>: supports system decorations</li>
+ * <li><pre>should_show_system_decorations</pre>: always shows system decorations</li>
+ * <li><pre>fixed_content_mode</pre>: does not allow the content mode switch</li>
* </ul>
* </p><p>
* Example:
@@ -20687,6 +20824,24 @@ public final class Settings {
@Readable
public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode";
+ /**
+ * Setting indicating whether the primary gesture input action has been enabled by the
+ * user.
+ *
+ * @hide
+ */
+ public static final String GESTURE_PRIMARY_ACTION_USER_PREFERENCE =
+ "gesture_primary_action_user_preference";
+
+ /**
+ * Setting indicating whether the dismiss gesture input action has been enabled by the
+ * user.
+ *
+ * @hide
+ */
+ public static final String GESTURE_DISMISS_ACTION_USER_PREFERENCE =
+ "gesture_dismiss_action_user_preference";
+
/** Whether Wear Power Anomaly Service is enabled.
*
* (0 = false, 1 = true)
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index f7f4eeca58e2..7d7087642fad 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4988,6 +4988,66 @@ public final class Telephony {
public static final String COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM =
"is_satellite_provisioned_for_non_ip_datagram";
+ /**
+ * TelephonyProvider column name for satellite entitlement barred plmns list separated by
+ * comma [,]. The value of this column is set based on entitlement query result for
+ * satellite configuration. Ex : 31026,302820,40445
+ * By default, it's empty.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_BARRED_PLMNS =
+ "satellite_entitlement_barred_plmns";
+
+
+ /**
+ * TelephonyProvider column name for satellite entitlement data plan for plmns which is
+ * built in Json format in Key:Value pair. The value of this column is set based on
+ * entitlement query result for satellite configuration.
+ * Ex : {"302820":0,"31026":1, "40445":0}
+ * By default, it's empty.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_DATA_PLAN_PLMNS =
+ "satellite_entitlement_data_plan_plmns";
+
+ /**
+ * TelephonyProvider column name for satellite entitlement service type map which is
+ * built in Json format in Key:Value pair. The value of this column is set based on
+ * entitlement query result for satellite configuration.
+ * Ex : {"302820":[1,3],"31026":[2,3],"40445":[1,3]}
+ * By default, it's empty.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_SERVICE_TYPE_MAP =
+ "satellite_entitlement_service_type_map";
+
+ /**
+ * TelephonyProvider column name for satellite entitlement data service policy type map
+ * which is built in Json format in Key:Value pair. The value of this column is set based
+ * on entitlement query result for satellite configuration.
+ * Ex : {"302820":2, "31026":1}
+ * By default, it's empty.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_DATA_SERVICE_POLICY =
+ "satellite_entitlement_data_service_policy";
+
+ /**
+ * TelephonyProvider column name for satellite entitlement voice service policy type map
+ * which is built in Json format in Key:Value pair. The value of this column is set
+ * based on entitlement query result for satellite configuration.
+ * Ex : {"302820":2, "31026":1}.
+ * By default, it's empty.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENTITLEMENT_VOICE_SERVICE_POLICY =
+ "satellite_entitlement_voice_service_policy";
+
/** All columns in {@link SimInfo} table. */
private static final List<String> ALL_COLUMNS = List.of(
COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5065,7 +5125,12 @@ public final class Telephony {
COLUMN_SATELLITE_ENTITLEMENT_STATUS,
COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
COLUMN_SATELLITE_ESOS_SUPPORTED,
- COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM
+ COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM,
+ COLUMN_SATELLITE_ENTITLEMENT_BARRED_PLMNS,
+ COLUMN_SATELLITE_ENTITLEMENT_DATA_PLAN_PLMNS,
+ COLUMN_SATELLITE_ENTITLEMENT_SERVICE_TYPE_MAP,
+ COLUMN_SATELLITE_ENTITLEMENT_DATA_SERVICE_POLICY,
+ COLUMN_SATELLITE_ENTITLEMENT_VOICE_SERVICE_POLICY
);
/**
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 770e234381c4..0b2239aa42b2 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -57,6 +57,7 @@ import java.util.concurrent.Executor;
@SystemService(Context.ADVANCED_PROTECTION_SERVICE)
public final class AdvancedProtectionManager {
private static final String TAG = "AdvancedProtectionMgr";
+ private static final String PKG_SETTINGS = "com.android.settings";
//TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
//when the appropriate flag is launched.
@@ -343,6 +344,7 @@ public final class AdvancedProtectionManager {
}
Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG);
+ intent.setPackage(PKG_SETTINGS);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId);
intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type);
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 3a3ea189b227..7013f7d705f8 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -52,13 +52,6 @@ flag {
}
flag {
- name: "deprecate_fsv_sig"
- namespace: "hardware_backed_security"
- description: "Feature flag for deprecating .fsv_sig"
- bug: "277916185"
-}
-
-flag {
name: "extend_vb_chain_to_updated_apk"
namespace: "hardware_backed_security"
description: "Use v4 signature and fs-verity to chain verification of allowlisted APKs to Verified Boot"
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index ae0b56e6f009..45a21beabd89 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -44,6 +44,16 @@ flag {
}
flag {
+ name: "notify_single_item_change_on_icon_load"
+ namespace: "intentresolver"
+ description: "ChooserGridAdapter to notify specific items change when the target icon is loaded (instead of all-item change)."
+ bug: "298193161"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fix_resolver_memory_leak"
is_exported: true
namespace: "intentresolver"
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index ce31e1ea7e38..df3b8baa40c8 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -455,7 +455,7 @@ public class DreamService extends Service implements Window.Callback {
// Simply wake up in the case the device is not locked.
if (!keyguardManager.isKeyguardLocked()) {
- wakeUp();
+ wakeUp(false);
return true;
}
@@ -477,11 +477,11 @@ public class DreamService extends Service implements Window.Callback {
if (!mInteractive) {
if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
- wakeUp();
+ wakeUp(false);
return true;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (mDebug) Slog.v(mTag, "Waking up on back key");
- wakeUp();
+ wakeUp(false);
return true;
}
return mWindow.superDispatchKeyEvent(event);
@@ -492,7 +492,7 @@ public class DreamService extends Service implements Window.Callback {
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
if (!mInteractive) {
if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent");
- wakeUp();
+ wakeUp(false);
return true;
}
return mWindow.superDispatchKeyShortcutEvent(event);
@@ -505,7 +505,7 @@ public class DreamService extends Service implements Window.Callback {
// but finish()es on any other kind of activity
if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) {
if (mDebug) Slog.v(mTag, "Waking up on touchEvent");
- wakeUp();
+ wakeUp(false);
return true;
}
return mWindow.superDispatchTouchEvent(event);
@@ -516,7 +516,7 @@ public class DreamService extends Service implements Window.Callback {
public boolean dispatchTrackballEvent(MotionEvent event) {
if (!mInteractive) {
if (mDebug) Slog.v(mTag, "Waking up on trackballEvent");
- wakeUp();
+ wakeUp(false);
return true;
}
return mWindow.superDispatchTrackballEvent(event);
@@ -527,7 +527,7 @@ public class DreamService extends Service implements Window.Callback {
public boolean dispatchGenericMotionEvent(MotionEvent event) {
if (!mInteractive) {
if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent");
- wakeUp();
+ wakeUp(false);
return true;
}
return mWindow.superDispatchGenericMotionEvent(event);
@@ -925,32 +925,37 @@ public class DreamService extends Service implements Window.Callback {
}
}
- private synchronized void updateDoze() {
- if (mDreamToken == null) {
- Slog.w(mTag, "Updating doze without a dream token.");
- return;
- }
+ /**
+ * Updates doze state. Note that this must be called on the mHandler.
+ */
+ private void updateDoze() {
+ mHandler.post(() -> {
+ if (mDreamToken == null) {
+ Slog.w(mTag, "Updating doze without a dream token.");
+ return;
+ }
- if (mDozing) {
- try {
- Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState
- + " mDozeScreenBrightness=" + mDozeScreenBrightness
- + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat);
- if (startAndStopDozingInBackground()) {
- mDreamManager.startDozingOneway(
- mDreamToken, mDozeScreenState, mDozeScreenStateReason,
- mDozeScreenBrightnessFloat, mDozeScreenBrightness,
- mUseNormalBrightnessForDoze);
- } else {
- mDreamManager.startDozing(
- mDreamToken, mDozeScreenState, mDozeScreenStateReason,
- mDozeScreenBrightnessFloat, mDozeScreenBrightness,
- mUseNormalBrightnessForDoze);
+ if (mDozing) {
+ try {
+ Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState
+ + " mDozeScreenBrightness=" + mDozeScreenBrightness
+ + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat);
+ if (startAndStopDozingInBackground()) {
+ mDreamManager.startDozingOneway(
+ mDreamToken, mDozeScreenState, mDozeScreenStateReason,
+ mDozeScreenBrightnessFloat, mDozeScreenBrightness,
+ mUseNormalBrightnessForDoze);
+ } else {
+ mDreamManager.startDozing(
+ mDreamToken, mDozeScreenState, mDozeScreenStateReason,
+ mDozeScreenBrightnessFloat, mDozeScreenBrightness,
+ mUseNormalBrightnessForDoze);
+ }
+ } catch (RemoteException ex) {
+ // system server died
}
- } catch (RemoteException ex) {
- // system server died
}
- }
+ });
}
/**
@@ -966,14 +971,20 @@ public class DreamService extends Service implements Window.Callback {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void stopDozing() {
- if (mDozing) {
- mDozing = false;
- try {
- mDreamManager.stopDozing(mDreamToken);
- } catch (RemoteException ex) {
- // system server died
+ mHandler.post(() -> {
+ if (mDreamToken == null) {
+ return;
}
- }
+
+ if (mDozing) {
+ mDozing = false;
+ try {
+ mDreamManager.stopDozing(mDreamToken);
+ } catch (RemoteException ex) {
+ // system server died
+ }
+ }
+ });
}
/**
@@ -1201,7 +1212,7 @@ public class DreamService extends Service implements Window.Callback {
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
- mHandler.post(() -> finish());
+ mHandler.post(() -> finishInternal());
}
@Override
@@ -1299,9 +1310,13 @@ public class DreamService extends Service implements Window.Callback {
* </p>
*/
public final void finish() {
+ mHandler.post(this::finishInternal);
+ }
+
+ private void finishInternal() {
// If there is an active overlay connection, signal that the dream is ending before
- // continuing. Note that the overlay cannot rely on the unbound state, since another dream
- // might have bound to it in the meantime.
+ // continuing. Note that the overlay cannot rely on the unbound state, since another
+ // dream might have bound to it in the meantime.
if (mOverlayConnection != null) {
mOverlayConnection.addConsumer(overlay -> {
try {
@@ -1357,7 +1372,7 @@ public class DreamService extends Service implements Window.Callback {
* </p>
*/
public final void wakeUp() {
- wakeUp(false);
+ mHandler.post(()-> wakeUp(false));
}
/**
@@ -1559,7 +1574,7 @@ public class DreamService extends Service implements Window.Callback {
if (mActivity != null && !mActivity.isFinishing()) {
mActivity.finishAndRemoveTask();
} else {
- finish();
+ finishInternal();
}
mDreamToken = null;
@@ -1719,7 +1734,7 @@ public class DreamService extends Service implements Window.Callback {
// the window reference in order to fully release the DreamActivity.
mWindow = null;
mActivity = null;
- finish();
+ finishInternal();
}
if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 505db30ca719..772fd968e507 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -225,7 +225,7 @@ public final class Adjustment implements Parcelable {
public static final int TYPE_CONTENT_RECOMMENDATION = 4;
/**
- * Data type: String, a summarization of the text of the notification, or, if provided for
+ * Data type: CharSequence, a summarization of the text of the notification, or, if provided for
* a group summary, a summarization of the text of all of the notificatrions in the group.
* Send this key with a null value to remove an existing summarization.
*/
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 7660ed96d30e..815444d195c9 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -219,7 +219,7 @@ public class NotificationRankingUpdate implements Parcelable {
// Gets a read/write buffer mapping the entire shared memory region.
buffer = mRankingMapFd.mapReadWrite();
// Puts the ranking map into the shared memory region buffer.
- buffer.put(mapParcel.marshall(), 0, mapSize);
+ mapParcel.marshall(buffer);
// Protects the region from being written to, by setting it to be read-only.
mRankingMapFd.setProtect(OsConstants.PROT_READ);
// Puts the SharedMemory object in the parcel.
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index dc7129cde5e5..ea7ee4addbe6 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -4,7 +4,10 @@
"name": "CtsNotificationTestCases_notification"
},
{
- "name": "FrameworksUiServicesTests_notification"
+ "name": "FrameworksUiServicesNotificationTests"
+ },
+ {
+ "name": "FrameworksUiServicesZenTests"
}
],
"postsubmit": [
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 1cf43d455be8..4cbd5beb3a8c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -2636,7 +2636,7 @@ public class ZenModeConfig implements Parcelable {
enabled = source.readInt() == 1;
snoozing = source.readInt() == 1;
if (source.readInt() == 1) {
- name = source.readString8();
+ name = source.readString();
}
zenMode = source.readInt();
conditionId = source.readParcelable(null, android.net.Uri.class);
@@ -2644,18 +2644,18 @@ public class ZenModeConfig implements Parcelable {
component = source.readParcelable(null, android.content.ComponentName.class);
configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
if (source.readInt() == 1) {
- id = source.readString8();
+ id = source.readString();
}
creationTime = source.readLong();
if (source.readInt() == 1) {
- enabler = source.readString8();
+ enabler = source.readString();
}
zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
- pkg = source.readString8();
+ pkg = source.readString();
allowManualInvocation = source.readBoolean();
- iconResName = source.readString8();
- triggerDescription = source.readString8();
+ iconResName = source.readString();
+ triggerDescription = source.readString();
type = source.readInt();
userModifiedFields = source.readInt();
zenPolicyUserModifiedFields = source.readInt();
@@ -2703,7 +2703,7 @@ public class ZenModeConfig implements Parcelable {
dest.writeInt(snoozing ? 1 : 0);
if (name != null) {
dest.writeInt(1);
- dest.writeString8(name);
+ dest.writeString(name);
} else {
dest.writeInt(0);
}
@@ -2714,23 +2714,23 @@ public class ZenModeConfig implements Parcelable {
dest.writeParcelable(configurationActivity, 0);
if (id != null) {
dest.writeInt(1);
- dest.writeString8(id);
+ dest.writeString(id);
} else {
dest.writeInt(0);
}
dest.writeLong(creationTime);
if (enabler != null) {
dest.writeInt(1);
- dest.writeString8(enabler);
+ dest.writeString(enabler);
} else {
dest.writeInt(0);
}
dest.writeParcelable(zenPolicy, 0);
dest.writeParcelable(zenDeviceEffects, 0);
- dest.writeString8(pkg);
+ dest.writeString(pkg);
dest.writeBoolean(allowManualInvocation);
- dest.writeString8(iconResName);
- dest.writeString8(triggerDescription);
+ dest.writeString(iconResName);
+ dest.writeString(triggerDescription);
dest.writeInt(type);
dest.writeInt(userModifiedFields);
dest.writeInt(zenPolicyUserModifiedFields);
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index e1dc6f6d642b..e9ddfc3559bf 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -96,6 +96,10 @@ class QuickAccessWalletServiceInfo {
defaultAppPackageName = defaultPaymentApp.getPackageName();
}
+ if (defaultAppPackageName == null || defaultAppUser < 0) {
+ return null;
+ }
+
ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName,
defaultAppUser);
if (serviceInfo == null) {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 937aecc8d718..0ace80875325 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -230,8 +230,8 @@ public abstract class HotwordDetectionService extends Service
}
@Override
- public void ping(IRemoteCallback callback) throws RemoteException {
- callback.sendResult(null);
+ public void ping(IPingMe callback) throws RemoteException {
+ callback.onPing();
}
@Override
diff --git a/core/java/android/service/voice/ISandboxedDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
index c76ac28eb36c..5c4503cb359c 100644
--- a/core/java/android/service/voice/ISandboxedDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -65,11 +65,15 @@ oneway interface ISandboxedDetectionService {
void updateRecognitionServiceManager(
in IRecognitionServiceManager recognitionServiceManager);
+ interface IPingMe {
+ void onPing();
+ }
+
/**
* Simply requests the service to trigger the callback, so that the system can check its
* identity.
*/
- void ping(in IRemoteCallback callback);
+ void ping(in IPingMe callback);
void stopDetection();
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index 8c9731d12df3..5dcaa3e976eb 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -122,8 +122,8 @@ public abstract class VisualQueryDetectionService extends Service
}
@Override
- public void ping(IRemoteCallback callback) throws RemoteException {
- callback.sendResult(null);
+ public void ping(IPingMe callback) throws RemoteException {
+ callback.onPing();
}
@Override
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 44c3f9a8244e..0152c52a6753 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -75,9 +75,12 @@ public abstract class Layout {
// These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h
private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 0f;
private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0f;
- private static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP = 5f;
// since we're not using soft light yet, this needs to be much lower than the spec'd 0.8
private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.7f;
+ @VisibleForTesting
+ static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP = 5f;
+ @VisibleForTesting
+ static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR = 0.5f;
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
@@ -1030,7 +1033,9 @@ public abstract class Layout {
var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
- var cornerRadius = mPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP;
+ var cornerRadius = Math.max(
+ mPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP,
+ mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR);
// We set the alpha on the color itself instead of Paint.setAlpha(), because that function
// actually mutates the color in... *ehem* very strange ways. Also the color might get reset
diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java
index cce3a0e3eaa9..e7ceaae964ef 100644
--- a/core/java/android/util/MapCollections.java
+++ b/core/java/android/util/MapCollections.java
@@ -355,7 +355,12 @@ abstract class MapCollections<K, V> {
}
return result;
}
- };
+
+ @Override
+ public String toString() {
+ return toStringHelper(0, this, "KeySet");
+ }
+ }
final class ValuesCollection implements Collection<V> {
@@ -456,7 +461,12 @@ abstract class MapCollections<K, V> {
public <T> T[] toArray(T[] array) {
return toArrayHelper(array, 1);
}
- };
+
+ @Override
+ public String toString() {
+ return toStringHelper(1, this, "ValuesCollection");
+ }
+ }
public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) {
Iterator<?> it = collection.iterator();
@@ -513,6 +523,29 @@ abstract class MapCollections<K, V> {
return array;
}
+ private String toStringHelper(int offset, Object thing, String thingName) {
+ int size = colGetSize();
+ if (size == 0) {
+ return "[]";
+ }
+
+ StringBuilder buffer = new StringBuilder(size * 14);
+ buffer.append('[');
+ for (int i = 0; i < size; i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ Object entry = colGetEntry(i, offset);
+ if (entry != thing) {
+ buffer.append(entry);
+ } else {
+ buffer.append("(this ").append(thingName).append(")");
+ }
+ }
+ buffer.append(']');
+ return buffer.toString();
+ }
+
public static <T> boolean equalsSetHelper(Set<T> set, Object object) {
if (set == object) {
return true;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 7c1e4976b9d3..3a2ec91b3b20 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -967,8 +967,11 @@ public final class Choreographer {
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
- boolean resynced = false;
+ // Original intended vsync time that is not adjusted by jitter
+ // or buffer stuffing recovery. Reported for jank tracking.
+ final long intendedFrameTimeNanos = frameTimeNanos;
long offsetFrameTimeNanos = frameTimeNanos;
+ boolean resynced = false;
// Evaluate if buffer stuffing recovery needs to start or end, and
// what actions need to be taken for recovery.
@@ -1012,7 +1015,6 @@ public final class Choreographer {
+ ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
- long intendedFrameTimeNanos = offsetFrameTimeNanos;
startNanos = System.nanoTime();
// Calculating jitter involves using the original frame time without
// adjustments from buffer stuffing
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 3f45e29f2d43..5c816543e191 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -286,7 +286,7 @@ public final class Display {
/**
* Display flag: Indicates that the display should show system decorations.
* <p>
- * This flag identifies secondary displays that should show system decorations, such as
+ * This flag identifies secondary displays that should always show system decorations, such as
* navigation bar, home activity or wallpaper.
* </p>
* <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
@@ -401,6 +401,18 @@ public final class Display {
public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 14;
/**
+ * Display flag: Indicates that the display is allowed to switch the content mode between
+ * projected/extended and mirroring. This allows the display to dynamically add or remove the
+ * home and system decorations.
+ *
+ * Note that this flag shouldn't be enabled with {@link #FLAG_PRIVATE} or
+ * {@link #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} at the same time; otherwise it will be ignored.
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOWS_CONTENT_MODE_SWITCH = 1 << 15;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index d880072aa404..bf000d5fa39a 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -1125,6 +1125,9 @@ public final class DisplayInfo implements Parcelable {
if ((flags & Display.FLAG_REAR) != 0) {
result.append(", FLAG_REAR_DISPLAY");
}
+ if ((flags & Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0) {
+ result.append(", FLAG_ALLOWS_CONTENT_MODE_SWITCH");
+ }
return result.toString();
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 6f346bdae70a..394ac8f8c6e9 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1986,7 +1986,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// report its requested visibility at the end of the animation, otherwise we would
// lose the leash, and it would disappear during the animation
// TODO(b/326377046) revisit this part and see if we can make it more general
- typesToReport = mRequestedVisibleTypes | (mAnimatingTypes & ime());
+ if (Flags.reportAnimatingInsetsTypes()) {
+ typesToReport = mRequestedVisibleTypes;
+ } else {
+ typesToReport = mRequestedVisibleTypes | (mAnimatingTypes & ime());
+ }
} else {
typesToReport = mRequestedVisibleTypes;
}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index aad3bf2679d9..901fc02f0040 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -516,8 +516,9 @@ public abstract class LayoutInflater {
mConstructorArgs[0] = inflaterContext;
View result = root;
- if (root != null && root.getViewRootImpl() != null) {
- root.getViewRootImpl().notifyRendererOfExpensiveFrame();
+ ViewRootImpl viewRootImpl = root != null ? root.getViewRootImpl() : null;
+ if (viewRootImpl != null) {
+ viewRootImpl.notifyRendererOfExpensiveFrame();
}
try {
diff --git a/core/java/android/view/ListenerWrapper.java b/core/java/android/view/ListenerWrapper.java
new file mode 100644
index 000000000000..fcf3fdb68112
--- /dev/null
+++ b/core/java/android/view/ListenerWrapper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * A utilty class to bundle a {@link Consumer} and an {@link Executor}
+ * @param <T> the type of value to be reported.
+ * @hide
+ */
+public class ListenerWrapper<T> {
+
+ @NonNull
+ private final Consumer<T> mConsumer;
+ @NonNull
+ private final Executor mExecutor;
+
+ public ListenerWrapper(@NonNull Executor executor, @NonNull Consumer<T> consumer) {
+ mExecutor = Objects.requireNonNull(executor);
+ mConsumer = Objects.requireNonNull(consumer);
+ }
+
+ /**
+ * Relays the new value to the {@link Consumer} using the {@link Executor}
+ */
+ public void accept(@NonNull T value) {
+ mExecutor.execute(() -> mConsumer.accept(value));
+ }
+
+ /**
+ * Returns {@code true} if the consumer matches the one provided in the constructor,
+ * {@code false} otherwise.
+ */
+ public boolean isConsumerSame(@NonNull Consumer<T> consumer) {
+ return mConsumer.equals(consumer);
+ }
+}
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index e567414c9b8a..beaf2118d4dc 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -385,6 +385,13 @@ public class NotificationTopLineView extends ViewGroup {
}
/**
+ * Returns whether the title is present.
+ */
+ public boolean isTitlePresent() {
+ return mTitle != null;
+ }
+
+ /**
* Determine if the given point is touching an active part of the top line.
*/
public boolean isInTouchRect(float x, float y) {
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9e97a8eb58aa..2895bf3f846a 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -21,7 +21,9 @@ import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.Activity;
+import android.app.ActivityThread;
import android.app.AppGlobals;
+import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
@@ -39,14 +41,13 @@ import android.util.SparseArray;
import android.util.TypedValue;
import android.view.flags.Flags;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
/**
* Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
*/
public class ViewConfiguration {
- private static final String TAG = "ViewConfiguration";
-
/**
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
@@ -349,6 +350,8 @@ public class ViewConfiguration {
*/
private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500;
+ private static ResourceCache sResourceCache = new ResourceCache();
+
private final boolean mConstructedWithContext;
private final int mEdgeSlop;
private final int mFadingEdgeLength;
@@ -374,7 +377,6 @@ public class ViewConfiguration {
private final int mOverscrollDistance;
private final int mOverflingDistance;
private final boolean mViewTouchScreenHapticScrollFeedbackEnabled;
- @UnsupportedAppUsage
private final boolean mFadingMarqueeEnabled;
private final long mGlobalActionsKeyTimeout;
private final float mVerticalScrollFactor;
@@ -468,14 +470,12 @@ public class ViewConfiguration {
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mScrollbarSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_scrollbarSize);
+ mScrollbarSize = res.getDimensionPixelSize(R.dimen.config_scrollbarSize);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
final TypedValue multiplierValue = new TypedValue();
- res.getValue(
- com.android.internal.R.dimen.config_ambiguousGestureMultiplier,
+ res.getValue(R.dimen.config_ambiguousGestureMultiplier,
multiplierValue,
true /*resolveRefs*/);
mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat());
@@ -488,8 +488,7 @@ public class ViewConfiguration {
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
if (!sHasPermanentMenuKeySet) {
- final int configVal = res.getInteger(
- com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+ final int configVal = res.getInteger(R.integer.config_overrideHasPermanentMenuKey);
switch (configVal) {
default:
@@ -516,32 +515,27 @@ public class ViewConfiguration {
}
}
- mFadingMarqueeEnabled = res.getBoolean(
- com.android.internal.R.bool.config_ui_enableFadingMarquee);
- mTouchSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
+ mFadingMarqueeEnabled = res.getBoolean(R.bool.config_ui_enableFadingMarquee);
+ mTouchSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationTouchSlop);
mHandwritingSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHandwritingSlop);
- mHoverSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHoverSlop);
+ R.dimen.config_viewConfigurationHandwritingSlop);
+ mHoverSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationHoverSlop);
mMinScrollbarTouchTarget = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScrollbarTouchTarget);
+ R.dimen.config_minScrollbarTouchTarget);
mPagingTouchSlop = mTouchSlop * 2;
mDoubleTapTouchSlop = mTouchSlop;
mHandwritingGestureLineMargin = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
+ R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
- mMinimumFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMinFlingVelocity);
- mMaximumFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+ mMinimumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMinFlingVelocity);
+ mMaximumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMaxFlingVelocity);
int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);
+ R.dimen.config_viewMinRotaryEncoderFlingVelocity);
int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
+ R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {
mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;
mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY;
@@ -551,8 +545,7 @@ public class ViewConfiguration {
}
int configRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen
+ res.getDimensionPixelSize(R.dimen
.config_rotaryEncoderAxisScrollTickInterval);
mRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
configRotaryEncoderHapticScrollFeedbackTickIntervalPixels > 0
@@ -560,41 +553,31 @@ public class ViewConfiguration {
: NO_HAPTIC_SCROLL_TICK_INTERVAL;
mRotaryEncoderHapticScrollFeedbackEnabled =
- res.getBoolean(
- com.android.internal.R.bool
+ res.getBoolean(R.bool
.config_viewRotaryEncoderHapticScrollFedbackEnabled);
- mGlobalActionsKeyTimeout = res.getInteger(
- com.android.internal.R.integer.config_globalActionsKeyTimeout);
+ mGlobalActionsKeyTimeout = res.getInteger(R.integer.config_globalActionsKeyTimeout);
- mHorizontalScrollFactor = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_horizontalScrollFactor);
- mVerticalScrollFactor = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_verticalScrollFactor);
+ mHorizontalScrollFactor = res.getDimensionPixelSize(R.dimen.config_horizontalScrollFactor);
+ mVerticalScrollFactor = res.getDimensionPixelSize(R.dimen.config_verticalScrollFactor);
mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean(
- com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
+ R.bool.config_showMenuShortcutsWhenKeyboardPresent);
- mMinScalingSpan = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScalingSpan);
+ mMinScalingSpan = res.getDimensionPixelSize(R.dimen.config_minScalingSpan);
- mScreenshotChordKeyTimeout = res.getInteger(
- com.android.internal.R.integer.config_screenshotChordKeyTimeout);
+ mScreenshotChordKeyTimeout = res.getInteger(R.integer.config_screenshotChordKeyTimeout);
mSmartSelectionInitializedTimeout = res.getInteger(
- com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis);
+ R.integer.config_smartSelectionInitializedTimeoutMillis);
mSmartSelectionInitializingTimeout = res.getInteger(
- com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis);
- mPreferKeepClearForFocusEnabled = res.getBoolean(
- com.android.internal.R.bool.config_preferKeepClearForFocus);
+ R.integer.config_smartSelectionInitializingTimeoutMillis);
+ mPreferKeepClearForFocusEnabled = res.getBoolean(R.bool.config_preferKeepClearForFocus);
mViewBasedRotaryEncoderScrollHapticsEnabledConfig =
- res.getBoolean(
- com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
+ res.getBoolean(R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
mViewTouchScreenHapticScrollFeedbackEnabled =
Flags.enableScrollFeedbackForTouch()
- ? res.getBoolean(
- com.android.internal.R.bool
- .config_viewTouchScreenHapticScrollFeedbackEnabled)
+ ? res.getBoolean(R.bool.config_viewTouchScreenHapticScrollFeedbackEnabled)
: false;
}
@@ -632,6 +615,7 @@ public class ViewConfiguration {
@VisibleForTesting
public static void resetCacheForTesting() {
sConfigurations.clear();
+ sResourceCache = new ResourceCache();
}
/**
@@ -707,7 +691,7 @@ public class ViewConfiguration {
* components.
*/
public static int getPressedStateDuration() {
- return PRESSED_STATE_DURATION;
+ return sResourceCache.getPressedStateDuration();
}
/**
@@ -752,7 +736,7 @@ public class ViewConfiguration {
* considered to be a tap.
*/
public static int getTapTimeout() {
- return TAP_TIMEOUT;
+ return sResourceCache.getTapTimeout();
}
/**
@@ -761,7 +745,7 @@ public class ViewConfiguration {
* considered to be a tap.
*/
public static int getJumpTapTimeout() {
- return JUMP_TAP_TIMEOUT;
+ return sResourceCache.getJumpTapTimeout();
}
/**
@@ -770,7 +754,7 @@ public class ViewConfiguration {
* double-tap.
*/
public static int getDoubleTapTimeout() {
- return DOUBLE_TAP_TIMEOUT;
+ return sResourceCache.getDoubleTapTimeout();
}
/**
@@ -782,7 +766,7 @@ public class ViewConfiguration {
*/
@UnsupportedAppUsage
public static int getDoubleTapMinTime() {
- return DOUBLE_TAP_MIN_TIME;
+ return sResourceCache.getDoubleTapMinTime();
}
/**
@@ -792,7 +776,7 @@ public class ViewConfiguration {
* @hide
*/
public static int getHoverTapTimeout() {
- return HOVER_TAP_TIMEOUT;
+ return sResourceCache.getHoverTapTimeout();
}
/**
@@ -803,7 +787,7 @@ public class ViewConfiguration {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static int getHoverTapSlop() {
- return HOVER_TAP_SLOP;
+ return sResourceCache.getHoverTapSlop();
}
/**
@@ -1044,7 +1028,7 @@ public class ViewConfiguration {
* in milliseconds.
*/
public static long getZoomControlsTimeout() {
- return ZOOM_CONTROLS_TIMEOUT;
+ return sResourceCache.getZoomControlsTimeout();
}
/**
@@ -1113,14 +1097,14 @@ public class ViewConfiguration {
* friction.
*/
public static float getScrollFriction() {
- return SCROLL_FRICTION;
+ return sResourceCache.getScrollFriction();
}
/**
* @return the default duration in milliseconds for {@link ActionMode#hide(long)}.
*/
public static long getDefaultActionModeHideDuration() {
- return ACTION_MODE_HIDE_DURATION_DEFAULT;
+ return sResourceCache.getDefaultActionModeHideDuration();
}
/**
@@ -1471,8 +1455,137 @@ public class ViewConfiguration {
return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
}
- private static final int getDisplayDensity(Context context) {
+ private static int getDisplayDensity(Context context) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (100.0f * metrics.density);
}
+
+ /**
+ * Fetches resource values statically and caches them locally for fast lookup. Note that these
+ * values will not be updated during the lifetime of a process, even if resource overlays are
+ * applied.
+ */
+ private static final class ResourceCache {
+
+ private int mPressedStateDuration = -1;
+ private int mTapTimeout = -1;
+ private int mJumpTapTimeout = -1;
+ private int mDoubleTapTimeout = -1;
+ private int mDoubleTapMinTime = -1;
+ private int mHoverTapTimeout = -1;
+ private int mHoverTapSlop = -1;
+ private long mZoomControlsTimeout = -1L;
+ private float mScrollFriction = -1f;
+ private long mDefaultActionModeHideDuration = -1L;
+
+ public int getPressedStateDuration() {
+ if (mPressedStateDuration < 0) {
+ Resources resources = getCurrentResources();
+ mPressedStateDuration = resources != null
+ ? resources.getInteger(R.integer.config_pressedStateDurationMillis)
+ : PRESSED_STATE_DURATION;
+ }
+ return mPressedStateDuration;
+ }
+
+ public int getTapTimeout() {
+ if (mTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_tapTimeoutMillis)
+ : TAP_TIMEOUT;
+ }
+ return mTapTimeout;
+ }
+
+ public int getJumpTapTimeout() {
+ if (mJumpTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mJumpTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_jumpTapTimeoutMillis)
+ : JUMP_TAP_TIMEOUT;
+ }
+ return mJumpTapTimeout;
+ }
+
+ public int getDoubleTapTimeout() {
+ if (mDoubleTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mDoubleTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_doubleTapTimeoutMillis)
+ : DOUBLE_TAP_TIMEOUT;
+ }
+ return mDoubleTapTimeout;
+ }
+
+ public int getDoubleTapMinTime() {
+ if (mDoubleTapMinTime < 0) {
+ Resources resources = getCurrentResources();
+ mDoubleTapMinTime = resources != null
+ ? resources.getInteger(R.integer.config_doubleTapMinTimeMillis)
+ : DOUBLE_TAP_MIN_TIME;
+ }
+ return mDoubleTapMinTime;
+ }
+
+ public int getHoverTapTimeout() {
+ if (mHoverTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mHoverTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_hoverTapTimeoutMillis)
+ : HOVER_TAP_TIMEOUT;
+ }
+ return mHoverTapTimeout;
+ }
+
+ public int getHoverTapSlop() {
+ if (mHoverTapSlop < 0) {
+ Resources resources = getCurrentResources();
+ mHoverTapSlop = resources != null
+ ? resources.getDimensionPixelSize(R.dimen.config_hoverTapSlop)
+ : HOVER_TAP_SLOP;
+ }
+ return mHoverTapSlop;
+ }
+
+ public long getZoomControlsTimeout() {
+ if (mZoomControlsTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mZoomControlsTimeout = resources != null
+ ? resources.getInteger(R.integer.config_zoomControlsTimeoutMillis)
+ : ZOOM_CONTROLS_TIMEOUT;
+ }
+ return mZoomControlsTimeout;
+ }
+
+ public float getScrollFriction() {
+ if (mScrollFriction < 0) {
+ Resources resources = getCurrentResources();
+ mScrollFriction = resources != null
+ ? resources.getFloat(R.dimen.config_scrollFriction)
+ : SCROLL_FRICTION;
+ }
+ return mScrollFriction;
+ }
+
+ public long getDefaultActionModeHideDuration() {
+ if (mDefaultActionModeHideDuration < 0) {
+ Resources resources = getCurrentResources();
+ mDefaultActionModeHideDuration = resources != null
+ ? resources.getInteger(R.integer.config_defaultActionModeHideDurationMillis)
+ : ACTION_MODE_HIDE_DURATION_DEFAULT;
+ }
+ return mDefaultActionModeHideDuration;
+ }
+
+ private static Resources getCurrentResources() {
+ if (!android.companion.virtualdevice.flags.Flags
+ .migrateViewconfigurationConstantsToResources()) {
+ return null;
+ }
+ Application application = ActivityThread.currentApplication();
+ Context context = application != null ? application.getApplicationContext() : null;
+ return context != null ? context.getResources() : null;
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9d0773f0a606..2edce5de7ace 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -555,8 +555,6 @@ public final class ViewRootImpl implements ViewParent,
@UiContext
public final Context mContext;
- private UiModeManager mUiModeManager;
-
@UnsupportedAppUsage
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
@@ -1836,6 +1834,7 @@ public final class ViewRootImpl implements ViewParent,
eventsToBeRegistered,
mBasePackageName);
+ // LINT.IfChange(fi_cb)
if (forceInvertColor()) {
if (mForceInvertObserver == null) {
mForceInvertObserver = new ContentObserver(mHandler) {
@@ -1844,7 +1843,6 @@ public final class ViewRootImpl implements ViewParent,
updateForceDarkMode();
}
};
-
final Uri[] urisToObserve = {
Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED),
@@ -1859,6 +1857,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
}
+ // LINT.ThenChange(/services/core/java/com/android/server/UiModeManagerService.java:fi_cb)
}
/**
@@ -2078,8 +2077,7 @@ public final class ViewRootImpl implements ViewParent,
// We also ignore dark theme, since the app developer can override the user's
// preference for dark mode in configuration.uiMode. Instead, we assume that both
// force invert and the system's dark theme are enabled.
- if (getUiModeManager().getForceInvertState() ==
- UiModeManager.FORCE_INVERT_TYPE_DARK) {
+ if (shouldApplyForceInvertDark()) {
final boolean isLightTheme =
a.getBoolean(R.styleable.Theme_isLightTheme, false);
// TODO: b/372558459 - Also check the background ColorDrawable color lightness
@@ -2107,6 +2105,14 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ private boolean shouldApplyForceInvertDark() {
+ final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
+ if (uiModeManager == null) {
+ return false;
+ }
+ return uiModeManager.getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK;
+ }
+
private void updateForceDarkMode() {
if (mAttachInfo.mThreadedRenderer == null) return;
if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) {
@@ -3473,6 +3479,9 @@ public final class ViewRootImpl implements ViewParent,
* TODO(b/260382739): Apply this to all windows.
*/
private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
+ if (com.android.window.flags.Flags.reduceUnnecessaryMeasure()) {
+ return true;
+ }
return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0;
}
@@ -9409,13 +9418,6 @@ public final class ViewRootImpl implements ViewParent,
return mAudioManager;
}
- private UiModeManager getUiModeManager() {
- if (mUiModeManager == null) {
- mUiModeManager = mContext.getSystemService(UiModeManager.class);
- }
- return mUiModeManager;
- }
-
private Vibrator getSystemVibrator() {
if (mVibrator == null) {
mVibrator = mContext.getSystemService(Vibrator.class);
@@ -13654,4 +13656,11 @@ public final class ViewRootImpl implements ViewParent,
ThreadedRenderer.preInitBufferAllocator();
}
}
+
+ /**
+ * @hide
+ */
+ public Choreographer getChoreographer() {
+ return mChoreographer;
+ }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 83dc79beb75e..315f1ba58529 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1514,6 +1514,44 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
/**
+ * Application or Activity level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property} that specifies
+ * whether this package or activity wants to allow safe region letterboxing. A safe
+ * region policy may be applied by the system to improve the user experience by ensuring that
+ * the activity does not have any content that is occluded and has the correct current
+ * window metrics.
+ *
+ * <p>Not setting the property at all defaults it to {@code true}. In such a case, the activity
+ * will be letterboxed in the safe region.
+ *
+ * <p>To not allow the safe region letterboxing, add this property to your app
+ * manifest and set the value to {@code false}. An app should ignore safe region
+ * letterboxing if it can handle bounds and insets from all four directions correctly when a
+ * request to go immersive is denied by the system. If the application does not allow safe
+ * region letterboxing, the system will not override this behavior.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING"
+ * android:value="false"/&gt;
+ * &lt;/application&gt;
+ * </pre>or
+ * <pre>
+ * &lt;activity&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING"
+ * android:value="false"/&gt;
+ * &lt;/activity&gt;
+ * </pre>
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ String PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING =
+ "android.window.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index a5da0c3ce5b1..624216776f42 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -44,6 +44,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.inputmethod.InputMethodManager;
+import android.view.translation.ListenerGroup;
import android.window.ITrustedPresentationListener;
import android.window.InputTransferToken;
import android.window.TrustedPresentationThresholds;
@@ -58,6 +59,7 @@ import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -147,6 +149,12 @@ public final class WindowManagerGlobal {
@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
+ /**
+ * The {@link ListenerGroup} that is associated to {@link #mViews}.
+ * @hide
+ */
+ @GuardedBy("mLock")
+ private final ListenerGroup<List<View>> mWindowViewsListenerGroup = new ListenerGroup<>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
@@ -319,6 +327,29 @@ public final class WindowManagerGlobal {
}
}
+ /**
+ * Adds a listener that will be notified whenever {@link #getWindowViews()} changes. The
+ * current value is provided immediately. If it was registered previously then this is ano op.
+ */
+ public void addWindowViewsListener(@NonNull Executor executor,
+ @NonNull Consumer<List<View>> consumer) {
+ synchronized (mLock) {
+ mWindowViewsListenerGroup.addListener(executor, consumer);
+ mWindowViewsListenerGroup.accept(getWindowViews());
+ }
+ }
+
+ /**
+ * Removes a listener that was registered in
+ * {@link #addWindowViewsListener(Executor, Consumer)}. If it was not registered previously,
+ * then this is a no op.
+ */
+ public void removeWindowViewsListener(@NonNull Consumer<List<View>> consumer) {
+ synchronized (mLock) {
+ mWindowViewsListenerGroup.removeListener(consumer);
+ }
+ }
+
public View getWindowView(IBinder windowToken) {
synchronized (mLock) {
final int numViews = mViews.size();
@@ -454,6 +485,7 @@ public final class WindowManagerGlobal {
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
+ mWindowViewsListenerGroup.accept(getWindowViews());
} catch (RuntimeException e) {
Log.e(TAG, "Couldn't add view: " + view, e);
final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
@@ -575,6 +607,7 @@ public final class WindowManagerGlobal {
mDyingViews.remove(view);
}
allViewsRemoved = mRoots.isEmpty();
+ mWindowViewsListenerGroup.accept(getWindowViews());
}
// If we don't have any views anymore in our process, we no longer need the
diff --git a/core/java/android/view/XrWindowProperties.java b/core/java/android/view/XrWindowProperties.java
new file mode 100644
index 000000000000..23021a563393
--- /dev/null
+++ b/core/java/android/view/XrWindowProperties.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * Class for XR-specific window properties to put in application manifests.
+ */
+@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+public final class XrWindowProperties {
+ /** @hide */
+ private XrWindowProperties() {}
+
+ /**
+ * Both Application and activity level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property} for an app to
+ * inform the system of the activity launch mode in XR. When it is declared at the application
+ * level, all activities are set to the defined value, unless it is overridden at the activity
+ * level.
+ *
+ * <p>The default value is {@link #XR_ACTIVITY_START_MODE_UNDEFINED}.
+ *
+ * <p>The available values are:
+ * <ul>
+ * <li>{@link #XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED}
+ * <li>{@link #XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED}
+ * <li>{@link #XR_ACTIVITY_START_MODE_HOME_SPACE}
+ * <li>{@link #XR_ACTIVITY_START_MODE_UNDEFINED}
+ * </ul>
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_ACTIVITY_XR_START_MODE"
+ * android:value="XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED|
+ * XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED|
+ * XR_ACTIVITY_START_MODE_HOME_SPACE|
+ * XR_ACTIVITY_START_MODE_UNDEFINED"/&gt;
+ * &lt;/application&gt;
+ * </pre>
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String PROPERTY_XR_ACTIVITY_START_MODE =
+ "android.window.PROPERTY_XR_ACTIVITY_START_MODE";
+
+ /**
+ * Defines the value to launch an activity in unmanaged full space mode in XR, where the
+ * activity itself is rendering the space and controls its own scene graph. This should be used
+ * for all activities that use OpenXR to render.
+ *
+ * @see #PROPERTY_XR_ACTIVITY_START_MODE
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED =
+ "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED";
+
+ /**
+ * The default value if not specified. If used, the actual launching mode will be determined by
+ * the system based on the launching activity's current mode and the launching flags. When
+ * {@link #PROPERTY_XR_ACTIVITY_START_MODE} is used at the application level, apps can use this
+ * value to reset at individual activity level.
+ *
+ * @see #PROPERTY_XR_ACTIVITY_START_MODE
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String XR_ACTIVITY_START_MODE_UNDEFINED =
+ "XR_ACTIVITY_START_MODE_UNDEFINED";
+
+ /**
+ * Defines the value to launch an activity in
+ * <a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/transition-home-space-to-full-space">managed
+ * full space mode</a> in XR, where the system is rendering the activity from a scene graph.
+ *
+ * @see #PROPERTY_XR_ACTIVITY_START_MODE
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED =
+ "XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED";
+
+ /**
+ * Defines the value to launch an activity in
+ * <a href="https://developer.android.com/develop/xr/jetpack-xr-sdk/transition-home-space-to-full-space">home
+ * space mode</a> in XR.
+ *
+ * @see #PROPERTY_XR_ACTIVITY_START_MODE
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String XR_ACTIVITY_START_MODE_HOME_SPACE =
+ "XR_ACTIVITY_START_MODE_HOME_SPACE";
+
+ /**
+ * Both Application and activity level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property} for an app to
+ * inform the system of the type of safety boundary recommended for the activity. When it is
+ * declared at the application level, all activities are set to the defined value, unless it is
+ * overridden at the activity level. When not declared, the system will not enforce any
+ * recommendations for a type of safety boundary and will continue to use the type that is
+ * currently in use.
+ *
+ * <p>The default value is {@link #XR_BOUNDARY_TYPE_NO_RECOMMENDATION}.
+ *
+ * <p>The available values are:
+ * <ul>
+ * <li>{@link #XR_BOUNDARY_TYPE_LARGE}
+ * <li>{@link #XR_BOUNDARY_TYPE_NO_RECOMMENDATION}
+ * </ul>
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED"
+ * android:value="XR_BOUNDARY_TYPE_LARGE|
+ * XR_BOUNDARY_TYPE_NO_RECOMMENDATION"/&gt;
+ * &lt;/application&gt;
+ * </pre>
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED =
+ "android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED";
+
+ /**
+ * Defines the value to launch an activity with no recommendations for the type of safety
+ * boundary. The system will continue to use the type of safety boundary that is currently
+ * in use.
+ *
+ * @see #PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String XR_BOUNDARY_TYPE_NO_RECOMMENDATION =
+ "XR_BOUNDARY_TYPE_NO_RECOMMENDATION";
+
+ /**
+ * Defines the value to launch an activity with a large boundary recommended. This is useful for
+ * activities which expect users to be moving around. The system will ask the user to use a
+ * larger size for their safety boundary and check that their space is clear, if the larger
+ * size is not already in use. This larger size will be determined by the system.
+ *
+ * @see #PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String XR_BOUNDARY_TYPE_LARGE = "XR_BOUNDARY_TYPE_LARGE";
+}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e43fb48527a1..c97c4acf706c 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -866,31 +866,18 @@ public final class AccessibilityManager {
}
/**
- * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
- * for a given feedback type.
- *
- * @param feedbackTypeFlags The feedback type flags.
- * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
- *
- * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
- * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
- * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
- * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
- * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
- * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
+ * @see #getEnabledAccessibilityServiceList(int)
+ * @hide
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
- int feedbackTypeFlags) {
+ int feedbackTypeFlags, int userId) {
final IAccessibilityManager service;
- final int userId;
synchronized (mLock) {
service = getServiceLocked();
if (service == null) {
return Collections.emptyList();
}
- userId = mUserId;
}
-
List<AccessibilityServiceInfo> services = null;
try {
services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
@@ -912,6 +899,29 @@ public final class AccessibilityManager {
}
/**
+ * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
+ * for a given feedback type.
+ *
+ * @param feedbackTypeFlags The feedback type flags.
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ *
+ * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+ * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+ * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+ * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+ * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
+ */
+ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+ int feedbackTypeFlags) {
+ final int userId;
+ synchronized (mLock) {
+ userId = mUserId;
+ }
+ return getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+ }
+
+ /**
* Returns whether the user must be shown the AccessibilityService warning dialog
* before the AccessibilityService (or any shortcut for the service) can be enabled.
* @hide
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index 8c98fa455cc8..3780db38ec84 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -21,6 +21,7 @@ flag {
namespace: "pixel_state_server"
description: "Feature flag to send a flush event after each frame"
bug: "380381249"
+ is_exported: true
is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index d06f885638b6..d97310494d34 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -159,3 +159,11 @@ flag {
bug: "364653005"
is_fixed_read_only: true
}
+
+flag {
+ name: "root_view_changed_listener"
+ namespace: "windowing_sdk"
+ description: "Implement listener pattern for WindowInspector#getGlobalWindowViews."
+ bug: "394397033"
+ is_fixed_read_only: false
+}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index eca798d6eb4f..290885593ee6 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -379,7 +379,7 @@ final class IInputMethodManagerGlobalInvoker {
@Nullable IRemoteInputConnection remoteInputConnection,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
final IInputMethodManager service = getService();
if (service == null) {
return InputBindResult.NULL;
@@ -388,7 +388,7 @@ final class IInputMethodManagerGlobalInvoker {
return service.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
- imeDispatcher);
+ imeDispatcher, imeRequestedVisible);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -408,7 +408,8 @@ final class IInputMethodManagerGlobalInvoker {
@Nullable IRemoteInputConnection remoteInputConnection,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ boolean useAsyncShowHideMethod) {
final IInputMethodManager service = getService();
if (service == null) {
return -1;
@@ -417,7 +418,8 @@ final class IInputMethodManagerGlobalInvoker {
service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken,
startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
- imeDispatcher, advanceAngGetStartInputSequenceNumber(), useAsyncShowHideMethod);
+ imeDispatcher, imeRequestedVisible, advanceAngGetStartInputSequenceNumber(),
+ useAsyncShowHideMethod);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0b34600f4104..a41ab368aed8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -871,6 +871,19 @@ public final class InputMethodManager {
IInputMethodManagerGlobalInvoker.reportPerceptibleAsync(windowToken, perceptible);
}
+ private static boolean hasViewImeRequestedVisible(View view) {
+ // before the refactor, the requestedVisibleTypes for the IME were not in sync with
+ // the state that was actually requested.
+ if (Flags.refactorInsetsController() && view != null) {
+ final var controller = view.getWindowInsetsController();
+ if (controller != null) {
+ return (view.getWindowInsetsController()
+ .getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0;
+ }
+ }
+ return false;
+ }
+
private final class DelegateImpl implements
ImeFocusController.InputMethodManagerDelegate {
@@ -941,6 +954,9 @@ public final class InputMethodManager {
Log.v(TAG, "Reporting focus gain, without startInput");
}
+ final boolean imeRequestedVisible = hasViewImeRequestedVisible(
+ mCurRootView.getView());
+
// ignore the result
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");
IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
@@ -950,7 +966,7 @@ public final class InputMethodManager {
null,
null, null,
mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
- UserHandle.myUserId(), mImeDispatcher);
+ UserHandle.myUserId(), mImeDispatcher, imeRequestedVisible);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -2441,9 +2457,8 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION);
if (resultReceiver != null) {
- final boolean imeReqVisible =
- (viewRootImpl.getInsetsController().getRequestedVisibleTypes()
- & WindowInsets.Type.ime()) != 0;
+ final boolean imeReqVisible = hasViewImeRequestedVisible(
+ viewRootImpl.getView());
resultReceiver.send(
imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_SHOWN, null);
@@ -2656,9 +2671,8 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
- final boolean imeReqVisible =
- (viewRootImpl.getInsetsController().getRequestedVisibleTypes()
- & WindowInsets.Type.ime()) != 0;
+ final boolean imeReqVisible = hasViewImeRequestedVisible(
+ viewRootImpl.getView());
if (resultReceiver != null) {
resultReceiver.send(
!imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_HIDDEN
@@ -3412,6 +3426,7 @@ public final class InputMethodManager {
final Handler icHandler;
InputBindResult res = null;
final boolean hasServedView;
+ final boolean imeRequestedVisible;
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
@@ -3479,10 +3494,13 @@ public final class InputMethodManager {
}
mServedInputConnection = servedInputConnection;
+ imeRequestedVisible = hasViewImeRequestedVisible(servedView);
+
if (DEBUG) {
Log.v(TAG, "START INPUT: view=" + InputMethodDebug.dumpViewInfo(view)
+ " ic=" + ic + " editorInfo=" + editorInfo + " startInputFlags="
- + InputMethodDebug.startInputFlagsToString(startInputFlags));
+ + InputMethodDebug.startInputFlagsToString(startInputFlags)
+ + " imeRequestedVisible=" + imeRequestedVisible);
}
// When we switch between non-editable views, do not call into the IMMS.
@@ -3513,7 +3531,7 @@ public final class InputMethodManager {
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher, mAsyncShowHideMethodEnabled);
+ mImeDispatcher, imeRequestedVisible, mAsyncShowHideMethodEnabled);
} else {
res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
@@ -3521,7 +3539,7 @@ public final class InputMethodManager {
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher);
+ mImeDispatcher, imeRequestedVisible);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (Flags.useZeroJankProxy()) {
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 3557f16a6dc8..ced27d6d4886 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,7 +28,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UiThread;
import android.app.UriGrantsManager;
import android.content.ContentProvider;
import android.content.Intent;
@@ -38,6 +37,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignalBeamer;
+import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -468,13 +468,27 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
});
}
+ /**
+ * Returns {@code false} if there is a sessionId mismatch and logs the event.
+ */
+ private boolean checkSessionId(@NonNull InputConnectionCommandHeader header) {
+ if (header.mSessionId != mCurrentSessionId.get()) {
+ Log.w(TAG, "Session id mismatch header.sessionId: " + header.mSessionId
+ + " currentSessionId: " + mCurrentSessionId.get() + " while calling "
+ + Debug.getCaller());
+ //TODO(b/396066692): log metrics.
+ return false; // cancelled
+ }
+ return true;
+ }
+
@Dispatching(cancellable = true)
@Override
public void getTextAfterCursor(InputConnectionCommandHeader header, int length, int flags,
AndroidFuture future /* T=CharSequence */) {
dispatchWithTracing("getTextAfterCursor", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -495,8 +509,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getTextBeforeCursor(InputConnectionCommandHeader header, int length, int flags,
AndroidFuture future /* T=CharSequence */) {
dispatchWithTracing("getTextBeforeCursor", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -517,8 +531,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getSelectedText(InputConnectionCommandHeader header, int flags,
AndroidFuture future /* T=CharSequence */) {
dispatchWithTracing("getSelectedText", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -539,8 +553,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) {
dispatchWithTracing("getSurroundingText", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -567,8 +581,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes,
AndroidFuture future /* T=Integer */) {
dispatchWithTracing("getCursorCapsMode", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return 0; // cancelled
+ if (!checkSessionId(header)) {
+ return 0;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -584,8 +598,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getExtractedText(InputConnectionCommandHeader header, ExtractedTextRequest request,
int flags, AndroidFuture future /* T=ExtractedText */) {
dispatchWithTracing("getExtractedText", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null;
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -601,8 +615,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitText(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition) {
dispatchWithTracing("commitText", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return;
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -618,8 +632,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("commitTextWithTextAttribute", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -634,8 +648,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void commitCompletion(InputConnectionCommandHeader header, CompletionInfo text) {
dispatchWithTracing("commitCompletion", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -650,8 +664,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void commitCorrection(InputConnectionCommandHeader header, CorrectionInfo info) {
dispatchWithTracing("commitCorrection", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -670,8 +684,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setSelection(InputConnectionCommandHeader header, int start, int end) {
dispatchWithTracing("setSelection", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -686,8 +700,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performEditorAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performEditorAction", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -702,8 +716,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performContextMenuAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performContextMenuAction", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -718,8 +732,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setComposingRegion(InputConnectionCommandHeader header, int start, int end) {
dispatchWithTracing("setComposingRegion", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -739,8 +753,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void setComposingRegionWithTextAttribute(InputConnectionCommandHeader header, int start,
int end, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("setComposingRegionWithTextAttribute", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -756,8 +770,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void setComposingText(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition) {
dispatchWithTracing("setComposingText", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -773,8 +787,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void setComposingTextWithTextAttribute(InputConnectionCommandHeader header,
CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("setComposingTextWithTextAttribute", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -826,8 +840,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
return;
}
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null && mDeactivateRequested.get()) {
@@ -842,8 +856,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) {
dispatchWithTracing("sendKeyEvent", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -858,8 +872,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) {
dispatchWithTracing("clearMetaKeyStates", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -875,8 +889,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength) {
dispatchWithTracing("deleteSurroundingText", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -892,8 +906,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void deleteSurroundingTextInCodePoints(InputConnectionCommandHeader header,
int beforeLength, int afterLength) {
dispatchWithTracing("deleteSurroundingTextInCodePoints", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -912,8 +926,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void beginBatchEdit(InputConnectionCommandHeader header) {
dispatchWithTracing("beginBatchEdit", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -928,8 +942,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void endBatchEdit(InputConnectionCommandHeader header) {
dispatchWithTracing("endBatchEdit", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -944,8 +958,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performSpellCheck(InputConnectionCommandHeader header) {
dispatchWithTracing("performSpellCheck", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -961,8 +975,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void performPrivateCommand(InputConnectionCommandHeader header, String action,
Bundle data) {
dispatchWithTracing("performPrivateCommand", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -995,12 +1009,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
}
}
dispatchWithTracing("performHandwritingGesture", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
+ if (!checkSessionId(header)) {
if (resultReceiver != null) {
resultReceiver.send(
InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null);
}
- return; // cancelled
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1038,9 +1052,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
(PreviewableHandwritingGesture) gestureContainer.get();
dispatchWithTracing("previewHandwritingGesture", () -> {
- if (header.mSessionId != mCurrentSessionId.get()
+ if (!checkSessionId(header)
|| (cancellationSignal != null && cancellationSignal.isCanceled())) {
- return; // cancelled
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1065,8 +1079,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
int imeDisplayId, AndroidFuture future /* T=Boolean */) {
dispatchWithTracing("requestCursorUpdates", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return false; // cancelled
+ if (!checkSessionId(header)) {
+ return false; // cancelled.
}
return requestCursorUpdatesInternal(
cursorUpdateMode, 0 /* cursorUpdateFilter */, imeDisplayId);
@@ -1079,8 +1093,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId,
AndroidFuture future /* T=Boolean */) {
dispatchWithTracing("requestCursorUpdates", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return false; // cancelled
+ if (!checkSessionId(header)) {
+ return false; // cancelled.
}
return requestCursorUpdatesInternal(
cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
@@ -1123,9 +1137,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
InputConnectionCommandHeader header, RectF bounds,
@NonNull ResultReceiver resultReceiver) {
dispatchWithTracing("requestTextBoundsInfo", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
+ if (!checkSessionId(header)) {
resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
- return; // cancelled
+ return; // cancelled
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1168,8 +1182,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
return false;
}
- if (header.mSessionId != mCurrentSessionId.get()) {
- return false; // cancelled
+ if (!checkSessionId(header)) {
+ return false; // cancelled.
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1193,8 +1207,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setImeConsumesInput(InputConnectionCommandHeader header, boolean imeConsumesInput) {
dispatchWithTracing("setImeConsumesInput", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1217,8 +1231,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
dispatchWithTracing(
"replaceText",
() -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1236,8 +1250,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitText(InputConnectionCommandHeader header, CharSequence text,
int newCursorPosition, @Nullable TextAttribute textAttribute) {
dispatchWithTracing("commitTextFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1256,8 +1270,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void setSelection(InputConnectionCommandHeader header, int start, int end) {
dispatchWithTracing("setSelectionFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1273,8 +1287,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) {
dispatchWithTracing("getSurroundingTextFromA11yIme", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return null; // cancelled
+ if (!checkSessionId(header)) {
+ return null; // cancelled.
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1301,8 +1315,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength,
int afterLength) {
dispatchWithTracing("deleteSurroundingTextFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1317,8 +1331,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) {
dispatchWithTracing("sendKeyEventFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1333,8 +1347,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performEditorAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performEditorActionFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1349,8 +1363,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void performContextMenuAction(InputConnectionCommandHeader header, int id) {
dispatchWithTracing("performContextMenuActionFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1366,8 +1380,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes,
AndroidFuture future /* T=Integer */) {
dispatchWithTracing("getCursorCapsModeFromA11yIme", future, () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return 0; // cancelled
+ if (!checkSessionId(header)) {
+ return 0; // cancelled.
}
final InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
@@ -1382,8 +1396,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Override
public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) {
dispatchWithTracing("clearMetaKeyStatesFromA11yIme", () -> {
- if (header.mSessionId != mCurrentSessionId.get()) {
- return; // cancelled
+ if (!checkSessionId(header)) {
+ return; // cancelled.
}
InputConnection ic = getInputConnection();
if (ic == null || mDeactivateRequested.get()) {
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index a4ea64e5811e..67e54423414c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -214,3 +214,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "invalidate_input_calls_restart"
+ namespace: "input_method"
+ description: "Feature flag to fix the race between invalidateInput and restartInput"
+ bug: "396066692"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/inspector/WindowInspector.java b/core/java/android/view/inspector/WindowInspector.java
index 69d004e860fd..3ebca3c9d9b6 100644
--- a/core/java/android/view/inspector/WindowInspector.java
+++ b/core/java/android/view/inspector/WindowInspector.java
@@ -16,11 +16,14 @@
package android.view.inspector;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.view.View;
import android.view.WindowManagerGlobal;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides access to window inspection information.
@@ -37,4 +40,25 @@ public final class WindowInspector {
public static List<View> getGlobalWindowViews() {
return WindowManagerGlobal.getInstance().getWindowViews();
}
+
+ /**
+ * Adds a listener that is notified whenever the list of global window views changes. If a
+ * {@link Consumer} is already registered this method is a no op.
+ * @see #getGlobalWindowViews()
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER)
+ public static void addGlobalWindowViewsListener(@NonNull Executor executor,
+ @NonNull Consumer<List<View>> consumer) {
+ WindowManagerGlobal.getInstance().addWindowViewsListener(executor, consumer);
+ }
+
+ /**
+ * Removes a listener from getting notifications of global window views changes. If the
+ * {@link Consumer} is not registered this method is a no op.
+ * @see #addGlobalWindowViewsListener(Executor, Consumer)
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER)
+ public static void removeGlobalWindowViewsListener(@NonNull Consumer<List<View>> consumer) {
+ WindowManagerGlobal.getInstance().removeWindowViewsListener(consumer);
+ }
}
diff --git a/core/java/android/view/translation/ListenerGroup.java b/core/java/android/view/translation/ListenerGroup.java
new file mode 100644
index 000000000000..bf506815f841
--- /dev/null
+++ b/core/java/android/view/translation/ListenerGroup.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.view.ListenerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * A utility class to manage a list of {@link ListenerWrapper}. This class is not thread safe.
+ * @param <T> the type of the value to be reported.
+ * @hide
+ */
+public class ListenerGroup<T> {
+ private final List<ListenerWrapper<T>> mListeners = new ArrayList<>();
+
+ /**
+ * Relays the value to all the registered {@link java.util.function.Consumer}
+ */
+ public void accept(@NonNull T value) {
+ Objects.requireNonNull(value);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).accept(value);
+ }
+ }
+
+ /**
+ * Adds a {@link Consumer} to the group. If the {@link Consumer} is already present then this
+ * is a no op.
+ */
+ public void addListener(@NonNull Executor executor, @NonNull Consumer<T> consumer) {
+ if (isContained(consumer)) {
+ return;
+ }
+ mListeners.add(new ListenerWrapper<>(executor, consumer));
+ }
+
+ /**
+ * Removes a {@link Consumer} from the group. If the {@link Consumer} was not present then this
+ * is a no op.
+ */
+ public void removeListener(@NonNull Consumer<T> consumer) {
+ final int index = computeIndex(consumer);
+ if (index > -1) {
+ mListeners.remove(index);
+ }
+ }
+
+ /**
+ * Returns {@code true} if the {@link Consumer} is present in the list, {@code false}
+ * otherwise.
+ */
+ private boolean isContained(Consumer<T> consumer) {
+ return computeIndex(consumer) > -1;
+ }
+
+ /**
+ * Returns the index of the matching {@link ListenerWrapper} if present, {@code -1} otherwise.
+ */
+ private int computeIndex(Consumer<T> consumer) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ if (mListeners.get(i).isConsumerSame(consumer)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/core/java/android/webkit/LegacyErrorStrings.java b/core/java/android/webkit/LegacyErrorStrings.java
index 60a6ee1a045f..f52214ca23ae 100644
--- a/core/java/android/webkit/LegacyErrorStrings.java
+++ b/core/java/android/webkit/LegacyErrorStrings.java
@@ -22,7 +22,7 @@ import android.util.Log;
/**
* Localized strings for the error codes defined in EventHandler.
*
- * {@hide}
+ * @hide
*/
class LegacyErrorStrings {
private LegacyErrorStrings() { /* Utility class, don't instantiate. */ }
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index b70565816037..76d2e4c632f6 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -73,7 +73,7 @@ public abstract class WebIconDatabase {
*/
public abstract void requestIconForPageUrl(String url, IconListener listener);
- /** {@hide}
+ /** @hide
*/
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 3f611c7efbdd..a328c78f3738 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -72,7 +72,7 @@ import java.util.function.Predicate;
/**
* <p>Displays a vertically-scrollable collection of views, where each view is positioned
- * immediatelybelow the previous view in the list. For a more modern, flexible, and performant
+ * immediately below the previous view in the list. For a more modern, flexible, and performant
* approach to displaying lists, use {@link androidx.recyclerview.widget.RecyclerView}.</p>
*
* <p>To display a list, you can include a list view in your layout XML file:</p>
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index cc2afbc6aaa3..d53c787749d9 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -18,7 +18,6 @@ package android.window;
import android.annotation.FloatRange;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.RemoteAnimationTarget;
@@ -39,8 +38,6 @@ public final class BackMotionEvent implements Parcelable {
@BackEvent.SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackMotionEvent} instance.
@@ -53,8 +50,6 @@ public final class BackMotionEvent implements Parcelable {
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param triggerBack Indicates whether the back arrow is in the triggered state or not
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing
- * application window.
*/
public BackMotionEvent(
float touchX,
@@ -62,15 +57,13 @@ public final class BackMotionEvent implements Parcelable {
long frameTimeMillis,
float progress,
boolean triggerBack,
- @BackEvent.SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ @BackEvent.SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mFrameTimeMillis = frameTimeMillis;
mProgress = progress;
mTriggerBack = triggerBack;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
}
private BackMotionEvent(@NonNull Parcel in) {
@@ -79,7 +72,6 @@ public final class BackMotionEvent implements Parcelable {
mProgress = in.readFloat();
mTriggerBack = in.readBoolean();
mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
mFrameTimeMillis = in.readLong();
}
@@ -108,7 +100,6 @@ public final class BackMotionEvent implements Parcelable {
dest.writeFloat(mProgress);
dest.writeBoolean(mTriggerBack);
dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
dest.writeLong(mFrameTimeMillis);
}
@@ -160,16 +151,6 @@ public final class BackMotionEvent implements Parcelable {
return mFrameTimeMillis;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackMotionEvent{"
@@ -179,7 +160,6 @@ public final class BackMotionEvent implements Parcelable {
+ ", mProgress=" + mProgress
+ ", mTriggerBack=" + mTriggerBack
+ ", mSwipeEdge=" + mSwipeEdge
- + ", mDepartingAnimationTarget=" + mDepartingAnimationTarget
+ "}";
}
}
diff --git a/core/java/android/window/BackTouchTracker.java b/core/java/android/window/BackTouchTracker.java
index 4908068d51e4..ea1b64066cfe 100644
--- a/core/java/android/window/BackTouchTracker.java
+++ b/core/java/android/window/BackTouchTracker.java
@@ -20,7 +20,6 @@ import android.annotation.FloatRange;
import android.os.SystemProperties;
import android.util.MathUtils;
import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
import java.io.PrintWriter;
@@ -147,15 +146,14 @@ public class BackTouchTracker {
}
/** Creates a start {@link BackMotionEvent}. */
- public BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+ public BackMotionEvent createStartEvent() {
return new BackMotionEvent(
/* touchX = */ mInitTouchX,
/* touchY = */ mInitTouchY,
/* frameTimeMillis = */ 0,
/* progress = */ 0,
/* triggerBack = */ mTriggerBack,
- /* swipeEdge = */ mSwipeEdge,
- /* departingAnimationTarget = */ target);
+ /* swipeEdge = */ mSwipeEdge);
}
/** Creates a progress {@link BackMotionEvent}. */
@@ -239,8 +237,7 @@ public class BackTouchTracker {
/* frameTimeMillis = */ 0,
/* progress = */ progress,
/* triggerBack = */ mTriggerBack,
- /* swipeEdge = */ mSwipeEdge,
- /* departingAnimationTarget = */ null);
+ /* swipeEdge = */ mSwipeEdge);
}
/** Sets the thresholds for computing progress. */
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index e0c48b03dad8..50b8bd22350c 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -47,26 +47,35 @@ public enum DesktopExperienceFlags {
com.android.server.display.feature.flags.Flags::baseDensityForExternalDisplays, true),
CONNECTED_DISPLAYS_CURSOR(com.android.input.flags.Flags::connectedDisplaysCursor, true),
DISPLAY_TOPOLOGY(com.android.server.display.feature.flags.Flags::displayTopology, true),
- ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY(Flags::enableBugFixesForSecondaryDisplay, false),
+ ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY(Flags::enableBugFixesForSecondaryDisplay, true),
ENABLE_CONNECTED_DISPLAYS_DND(Flags::enableConnectedDisplaysDnd, false),
ENABLE_CONNECTED_DISPLAYS_PIP(Flags::enableConnectedDisplaysPip, false),
- ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, false),
+ ENABLE_CONNECTED_DISPLAYS_WALLPAPER(
+ android.app.Flags::enableConnectedDisplaysWallpaper, false),
+ ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, true),
ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
true),
- ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, false),
- ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, false),
+ ENABLE_DISPLAY_DISCONNECT_INTERACTION(Flags::enableDisplayDisconnectInteraction, false),
+ ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, true),
+ ENABLE_DISPLAY_RECONNECT_INTERACTION(Flags::enableDisplayReconnectInteraction, false),
+ ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, true),
ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true),
- ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, false),
+ ENABLE_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS(Flags::keyboardShortcutsToSwitchDesks, false),
+ ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, true),
ENABLE_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false),
ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false),
+ ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS(
+ Flags::enablePersistingDisplaySizeForConnectedDisplays, false),
ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY(Flags::enablePerDisplayDesktopWallpaperActivity,
false),
ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF(
Flags::enablePerDisplayPackageContextCacheInStatusbarNotif, false),
- ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, false),
+ ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE(Flags::enableProjectedDisplayDesktopMode, false),
+ ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, true),
ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS(Flags::enterDesktopByDefaultOnFreeformDisplays,
- false),
+ true),
+ FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH(Flags::formFactorBasedDesktopFirstSwitch, false),
REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true)
// go/keep-sorted end
;
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 1b8f73a7edb8..5b3044e1988a 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -53,12 +53,14 @@ public enum DesktopModeFlags {
ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
Flags::enableCaptionCompatInsetForceConsumptionAlways, true),
ENABLE_CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
+ ENABLE_DESKTOP_APP_HANDLE_ANIMATION(Flags::enableDesktopAppHandleAnimation, false),
ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
true),
ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
+ ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX(Flags::enableDesktopImmersiveDragBugfix, false),
ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX(
Flags::enableDesktopIndicatorInSeparateThreadBugfix, false),
ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX(
@@ -92,6 +94,7 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
Flags::enableDesktopWindowingMultiInstanceFeatures, true),
ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, true),
+ ENABLE_DESKTOP_WINDOWING_PIP(Flags::enableDesktopWindowingPip, false),
ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
ENABLE_DESKTOP_WINDOWING_SCVH_CACHE(Flags::enableDesktopWindowingScvhCacheBugFix, true),
ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
@@ -106,10 +109,11 @@ public enum DesktopModeFlags {
ENABLE_FULLY_IMMERSIVE_IN_DESKTOP(Flags::enableFullyImmersiveInDesktop, true),
ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
+ ENABLE_INPUT_LAYER_TRANSITION_FIX(Flags::enableInputLayerTransitionFix, false),
ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false),
ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS(
- Flags::enableOpaqueBackgroundForTransparentWindows, false),
+ Flags::enableOpaqueBackgroundForTransparentWindows, true),
ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX(Flags::enableQuickswitchDesktopSplitBugfix, true),
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
@@ -134,10 +138,16 @@ public enum DesktopModeFlags {
ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
Flags::enableWindowingTransitionHandlersObservers, false),
EXCLUDE_CAPTION_FROM_APP_BOUNDS(Flags::excludeCaptionFromAppBounds, false),
+ FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK(
+ Flags::forceCloseTopTransparentFullscreenTask, false),
IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES(
Flags::ignoreAspectRatioRestrictionsForResizeableFreeformActivities, true),
INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
- Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true)
+ Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true),
+ INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES(
+ Flags::inheritTaskBoundsForTrampolineTaskLaunches, false),
+ SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX(
+ Flags::skipDecorViewRelayoutWhenClosingBugfix, false),
// go/keep-sorted end
;
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 6f4dd4e3c5ed..0b84070c8d26 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -66,17 +66,6 @@ interface IWindowOrganizerController {
void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
/**
- * Starts a legacy transition.
- * @param type The transition type.
- * @param adapter The animation to use.
- * @param syncCallback A sync callback for the contents of `t`
- * @param t Operations that are part of the transition.
- * @return sync-id or -1 if this no-op'd because a transition is already running.
- */
- int startLegacyTransition(int type, in RemoteAnimationAdapter adapter,
- in IWindowContainerTransactionCallback syncCallback, in WindowContainerTransaction t);
-
- /**
* Finishes a transition. This must be called for all created transitions.
* @param transitionToken Which transition to finish
* @param t Changes to make before finishing but in the same SF Transaction. Can be null.
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index d478108d928a..69613a748884 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -270,8 +270,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
}
mIOnBackInvokedCallback.onBackStarted(
new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
- backEvent.getProgress(), false, backEvent.getSwipeEdge(),
- null));
+ backEvent.getProgress(), false, backEvent.getSwipeEdge()));
} catch (RemoteException e) {
Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
}
@@ -286,8 +285,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
}
mIOnBackInvokedCallback.onBackProgressed(
new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
- backEvent.getProgress(), false, backEvent.getSwipeEdge(),
- null));
+ backEvent.getProgress(), false, backEvent.getSwipeEdge()));
} catch (RemoteException e) {
Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 1156503cf8e8..2ed9c3a48add 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -445,6 +445,27 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ /**
+ * Sets a given safe region {@code Rect} on the {@code container}. Set {@code null} to reset
+ * safe region bounds. When a safe region is set on a WindowContainer, the activities which
+ * need to be within a safe region will be letterboxed within the set safe region bounds.
+ * <p>Note that if the position of the WindowContainer changes, the caller needs to update the
+ * safe region bounds.
+ *
+ * @param container The window container that the safe region bounds are set on
+ * @param safeRegionBounds The rect for the safe region bounds which are absolute in nature.
+ * @hide
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public WindowContainerTransaction setSafeRegionBounds(
+ @NonNull WindowContainerToken container,
+ @Nullable Rect safeRegionBounds) {
+ mHierarchyOps.add(
+ HierarchyOp.createForSetSafeRegionBounds(container.asBinder(), safeRegionBounds));
+ return this;
+ }
+
/*
* ===========================================================================================
* Hierarchy updates (create/destroy/reorder/reparent containers)
@@ -684,16 +705,13 @@ public final class WindowContainerTransaction implements Parcelable {
* organizer.
* @param root1 the first root.
* @param root2 the second root.
+ * @deprecated replace with {@link #setAdjacentRootSet}
*/
+ @SuppressWarnings("UnflaggedApi") // @TestApi without associated feature.
+ @Deprecated
@NonNull
public WindowContainerTransaction setAdjacentRoots(
@NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
- root1.asBinder(),
- root2.asBinder()));
- return this;
- }
return setAdjacentRootSet(root1, root2);
}
@@ -710,14 +728,10 @@ public final class WindowContainerTransaction implements Parcelable {
*
* @param roots the Tasks that should be adjacent to each other.
* @throws IllegalArgumentException if roots have size < 2.
- * @hide // TODO(b/373709676) Rename to setAdjacentRoots and update CTS.
+ * @hide // TODO(b/373709676) Rename to setAdjacentRoots and update CTS in 25Q4.
*/
@NonNull
public WindowContainerTransaction setAdjacentRootSet(@NonNull WindowContainerToken... roots) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalArgumentException("allowMultipleAdjacentTaskFragments is not enabled."
- + " Use #setAdjacentRoots instead.");
- }
if (roots.length < 2) {
throw new IllegalArgumentException("setAdjacentRootSet must have size >= 2");
}
@@ -1013,7 +1027,7 @@ public final class WindowContainerTransaction implements Parcelable {
/**
* Sets to TaskFragments adjacent to each other. Containers below two visible adjacent
* TaskFragments will be made invisible. This is similar to
- * {@link #setAdjacentRoots(WindowContainerToken, WindowContainerToken)}, but can be used with
+ * {@link #setAdjacentRootSet(WindowContainerToken...)}, but can be used with
* fragmentTokens when that TaskFragments haven't been created (but will be created in the same
* {@link WindowContainerTransaction}).
* @param fragmentToken1 client assigned unique token to create TaskFragment with specified
@@ -1604,6 +1618,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23;
public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24;
public static final int HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY = 25;
+ public static final int HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS = 26;
@IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
HIERARCHY_OP_TYPE_REPARENT,
@@ -1632,6 +1647,7 @@ public final class WindowContainerTransaction implements Parcelable {
HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT,
HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY,
+ HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HierarchyOpType {
@@ -1717,6 +1733,9 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mLaunchAdjacentDisabled;
+ @Nullable
+ private Rect mSafeRegionBounds;
+
/** Creates a hierarchy operation for reparenting a container within the hierarchy. */
@NonNull
public static HierarchyOp createForReparent(
@@ -1880,6 +1899,17 @@ public final class WindowContainerTransaction implements Parcelable {
.build();
}
+ /** Creates a hierarchy op for setting the safe region bounds. */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public static HierarchyOp createForSetSafeRegionBounds(@NonNull IBinder container,
+ @Nullable Rect safeRegionBounds) {
+ return new Builder(HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS)
+ .setContainer(container)
+ .setSafeRegionBounds(safeRegionBounds)
+ .build();
+ }
+
/** Only creates through {@link Builder}. */
private HierarchyOp(@HierarchyOpType int type) {
mType = type;
@@ -1910,6 +1940,7 @@ public final class WindowContainerTransaction implements Parcelable {
mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
mLaunchAdjacentDisabled = copy.mLaunchAdjacentDisabled;
+ mSafeRegionBounds = copy.mSafeRegionBounds;
}
private HierarchyOp(@NonNull Parcel in) {
@@ -1937,6 +1968,7 @@ public final class WindowContainerTransaction implements Parcelable {
mIsTrimmableFromRecents = in.readBoolean();
mExcludeInsetsTypes = in.readInt();
mLaunchAdjacentDisabled = in.readBoolean();
+ mSafeRegionBounds = in.readTypedObject(Rect.CREATOR);
}
@HierarchyOpType
@@ -1973,13 +2005,6 @@ public final class WindowContainerTransaction implements Parcelable {
return mContainers;
}
- /** @deprecated b/373709676 replace with {@link #getContainers()}. */
- @Deprecated
- @NonNull
- public IBinder getAdjacentRoot() {
- return mReparent;
- }
-
public boolean getToTop() {
return mToTop;
}
@@ -2065,6 +2090,12 @@ public final class WindowContainerTransaction implements Parcelable {
return mLaunchAdjacentDisabled;
}
+ /** Denotes the safe region bounds */
+ @Nullable
+ public Rect getSafeRegionBounds() {
+ return mSafeRegionBounds;
+ }
+
/** Gets a string representation of a hierarchy-op type. */
public static String hopToString(@HierarchyOpType int type) {
switch (type) {
@@ -2098,6 +2129,7 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav";
case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState";
+ case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: return "setSafeRegionBounds";
default: return "HOP(" + type + ")";
}
}
@@ -2127,17 +2159,12 @@ public final class WindowContainerTransaction implements Parcelable {
sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom");
break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- for (IBinder container : mContainers) {
- if (container == mContainers[0]) {
- sb.append("adjacentRoots=").append(container);
- } else {
- sb.append(", ").append(container);
- }
+ for (IBinder container : mContainers) {
+ if (container == mContainers[0]) {
+ sb.append("adjacentRoots=").append(container);
+ } else {
+ sb.append(", ").append(container);
}
- } else {
- sb.append("container=").append(mContainer)
- .append(" adjacentRoot=").append(mReparent);
}
break;
case HIERARCHY_OP_TYPE_LAUNCH_TASK:
@@ -2203,6 +2230,11 @@ public final class WindowContainerTransaction implements Parcelable {
sb.append("container= ").append(mContainer)
.append(" isTrimmable= ")
.append(mIsTrimmableFromRecents);
+ break;
+ case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS:
+ sb.append("container= ").append(mContainer)
+ .append(" safeRegionBounds= ")
+ .append(mSafeRegionBounds);
default:
sb.append("container=").append(mContainer)
.append(" reparent=").append(mReparent)
@@ -2239,6 +2271,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeBoolean(mIsTrimmableFromRecents);
dest.writeInt(mExcludeInsetsTypes);
dest.writeBoolean(mLaunchAdjacentDisabled);
+ dest.writeTypedObject(mSafeRegionBounds, flags);
}
@Override
@@ -2324,6 +2357,9 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mLaunchAdjacentDisabled;
+ @Nullable
+ private Rect mSafeRegionBounds;
+
Builder(@HierarchyOpType int type) {
mType = type;
}
@@ -2445,6 +2481,11 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ Builder setSafeRegionBounds(Rect safeRegionBounds) {
+ mSafeRegionBounds = safeRegionBounds;
+ return this;
+ }
+
@NonNull
HierarchyOp build() {
final HierarchyOp hierarchyOp = new HierarchyOp(mType);
@@ -2475,7 +2516,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;
hierarchyOp.mLaunchAdjacentDisabled = mLaunchAdjacentDisabled;
-
+ hierarchyOp.mSafeRegionBounds = mSafeRegionBounds;
return hierarchyOp;
}
}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 5c5da49fe543..6e56b6318727 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -130,27 +130,6 @@ public class WindowOrganizer {
}
/**
- * Start a legacy transition.
- * @param type The type of the transition. This is ignored if a transitionToken is provided.
- * @param adapter An existing transition to start. If null, a new transition is created.
- * @param t The set of window operations that are part of this transition.
- * @return true on success, false if a transition was already running.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- @NonNull
- public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
- @NonNull WindowContainerTransactionCallback syncCallback,
- @NonNull WindowContainerTransaction t) {
- try {
- return getWindowOrganizerController().startLegacyTransition(
- type, adapter, syncCallback.mInterface, t);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Register an ITransitionPlayer to handle transition animations.
* @hide
*/
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index a42759e9e23f..cc07616412b6 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -155,4 +155,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "backup_and_restore_for_user_aspect_ratio_settings"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether B&R for user aspect ratio settings is enabled"
+ bug: "396650383"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 831a9dfc141b..afc9660c1a3a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -27,6 +27,17 @@ flag {
}
flag {
+ name: "inherit_task_bounds_for_trampoline_task_launches"
+ namespace: "lse_desktop_experience"
+ description: "Forces trampoline task launches to inherit the bounds of the previous instance /n"
+ "before is closes to prevent each task from cascading."
+ bug: "392815318"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "include_top_transparent_fullscreen_task_in_desktop_heuristic"
namespace: "lse_desktop_experience"
description: "Whether to include any top transparent fullscreen task launched in desktop /n"
@@ -50,6 +61,17 @@ flag {
}
flag {
+ name: "force_close_top_transparent_fullscreen_task"
+ namespace: "lse_desktop_experience"
+ description: "If a top transparent fullscreen task is on top of desktop mode, force it to /n"
+ "close if another task is opened or brought to front."
+ bug: "395041610"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_windowing_dynamic_initial_bounds"
namespace: "lse_desktop_experience"
description: "Enables new initial bounds for desktop windowing which adjust depending on app constraints"
@@ -154,6 +176,16 @@ flag {
}
flag {
+ name: "enable_input_layer_transition_fix"
+ namespace: "lse_desktop_experience"
+ description: "Enables a bugfix for input layer disposal during certain transitions."
+ bug: "371473978"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_accessible_custom_headers"
namespace: "lse_desktop_experience"
description: "Enables a11y-friendly custom header input handling"
@@ -570,6 +602,13 @@ flag {
}
flag {
+ name: "keyboard_shortcuts_to_switch_desks"
+ namespace: "lse_desktop_experience"
+ description: "Enable switching the active desk with keyboard shortcuts"
+ bug: "389957556"
+}
+
+flag {
name: "enable_connected_displays_dnd"
namespace: "lse_desktop_experience"
description: "Enable drag-and-drop between connected displays."
@@ -803,6 +842,26 @@ flag {
}
flag {
+ name: "enable_desktop_app_handle_animation"
+ namespace: "lse_desktop_experience"
+ description: "Enables the animation that occurs when the app handle of a task in immersive mode is shown or hidden."
+ bug: "375252977"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_desktop_immersive_drag_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Keeps the app handle visible during a drag."
+ bug: "381280828"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_desktop_indicator_in_separate_thread_bugfix"
namespace: "lse_desktop_experience"
description: "Enables running visual indicator view operations in ShellDesktopThread."
@@ -827,13 +886,10 @@ flag {
}
flag {
- name: "enable_persisting_density_scale_for_connected_displays"
+ name: "enable_persisting_display_size_for_connected_displays"
namespace: "lse_desktop_experience"
- description: "Enables persisting density scale on resolution change for connected displays."
+ description: "Enables persisting display size on resolution change for connected displays."
bug: "392855657"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
}
flag {
@@ -875,3 +931,51 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "skip_decor_view_relayout_when_closing_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables bugfix to skip DecorView relayout when the corresponding window is closing."
+ bug: "394502142"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_size_compat_mode_improvements_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enable some improvements in size compat mode for connected displays."
+ bug: "399752440"
+}
+
+flag {
+ name: "form_factor_based_desktop_first_switch"
+ namespace: "lse_desktop_experience"
+ description: "Enables the desktop-first mode switching logic based on its form factor."
+ bug: "394736817"
+}
+
+flag {
+ name: "enable_restart_menu_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enable restart menu UI, which is shown when an app moves between displays."
+ bug: "397804287"
+}
+
+flag {
+ name: "enable_dynamic_radius_computation_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables bugfix to compute the corner/shadow radius of desktop windows dynamically with the current window context."
+ bug: "399630464"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "show_home_behind_desktop"
+ namespace: "lse_desktop_experience"
+ description: "Enables the home to be shown behind the desktop."
+ bug: "375644149"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 0e19eb2d3219..816270235446 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -9,16 +9,6 @@ flag {
}
flag {
- name: "wait_for_transition_on_display_switch"
- namespace: "windowing_frontend"
- description: "Waits for Shell transition to start before unblocking the screen after display switch"
- bug: "301420598"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "apply_lifecycle_on_pip_change"
namespace: "windowing_frontend"
description: "Make pip activity lifecyle change with windowing mode"
@@ -29,10 +19,10 @@ flag {
}
flag {
- name: "respect_animation_clip"
+ name: "cache_window_style"
namespace: "windowing_frontend"
- description: "Fix missing clip transformation of animation"
- bug: "376601866"
+ description: "Cache common window styles"
+ bug: "350394503"
is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
@@ -40,10 +30,10 @@ flag {
}
flag {
- name: "cache_window_style"
+ name: "use_cached_insets_for_display_switch"
namespace: "windowing_frontend"
- description: "Cache common window styles"
- bug: "350394503"
+ description: "Reduce intermediate insets changes for display switch"
+ bug: "266197298"
is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
@@ -75,6 +65,14 @@ flag {
}
flag {
+ name: "action_mode_edge_to_edge"
+ namespace: "windowing_frontend"
+ description: "Make contextual action bar edge-to-edge"
+ bug: "379783298"
+ is_fixed_read_only: true
+}
+
+flag {
name: "keyguard_going_away_timeout"
namespace: "windowing_frontend"
description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting"
@@ -278,6 +276,17 @@ flag {
}
flag {
+ name: "reduce_unnecessary_measure"
+ namespace: "windowing_frontend"
+ description: "Skip measuring view hierarchy if the size is known"
+ bug: "260382739"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "ensure_wallpaper_in_transitions"
namespace: "windowing_frontend"
description: "Ensure that wallpaper window tokens are always present/available for collection in transitions"
@@ -482,4 +491,26 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "early_launch_hint"
+ namespace: "windowing_frontend"
+ description: "Sets Launch powermode for activity launches earlier"
+ bug: "399380676"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "scramble_snapshot_file_name"
+ namespace: "windowing_frontend"
+ description: "Scramble the file name of task snapshot."
+ bug: "293139053"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f2efa200918c..94e87c7a034b 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -81,6 +81,16 @@ flag {
flag {
namespace: "windowing_sdk"
+ name: "activity_embedding_delay_task_fragment_finish_for_activity_launch"
+ description: "Fixes a race condition that we finish the TaskFragment too early when there is a pending activity launch."
+ bug: "390452023"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "windowing_sdk"
name: "wlinfo_oncreate"
description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()"
bug: "337820752"
@@ -95,17 +105,6 @@ flag {
flag {
namespace: "windowing_sdk"
- name: "allow_multiple_adjacent_task_fragments"
- description: "Refactor to allow more than 2 adjacent TaskFragments"
- bug: "373709676"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "track_system_ui_context_before_wms"
description: "Keep track of SystemUiContext before WMS is initialized"
bug: "384428048"
@@ -127,17 +126,6 @@ flag {
flag {
namespace: "windowing_sdk"
- name: "use_self_sync_transaction_for_layer"
- description: "Always use this.getSyncTransaction for assignLayer"
- bug: "388127825"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "safe_region_letterboxing"
description: "Enables letterboxing for a safe region"
bug: "380132497"
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 1281a78d4fa2..24e80209e9e9 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -136,7 +136,8 @@ public class AccessibilityShortcutController {
private final Context mContext;
private final Handler mHandler;
- private final UserSetupCompleteObserver mUserSetupCompleteObserver;
+ @VisibleForTesting
+ public final UserSetupCompleteObserver mUserSetupCompleteObserver;
private AlertDialog mAlertDialog;
private boolean mIsShortcutEnabled;
@@ -471,7 +472,7 @@ public class AccessibilityShortcutController {
AccessibilityManager accessibilityManager =
mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
return accessibilityManager.getEnabledAccessibilityServiceList(
- FEEDBACK_ALL_MASK).contains(serviceInfo);
+ FEEDBACK_ALL_MASK, mUserId).contains(serviceInfo);
}
private boolean hasFeatureLeanback() {
@@ -676,7 +677,8 @@ public class AccessibilityShortcutController {
}
}
- private class UserSetupCompleteObserver extends ContentObserver {
+ @VisibleForTesting
+ public class UserSetupCompleteObserver extends ContentObserver {
private boolean mIsRegistered = false;
private int mUserId;
@@ -749,7 +751,8 @@ public class AccessibilityShortcutController {
R.string.config_defaultAccessibilityService);
final List<AccessibilityServiceInfo> enabledServices =
mFrameworkObjectProvider.getAccessibilityManagerInstance(
- mContext).getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+ mContext).getEnabledAccessibilityServiceList(
+ FEEDBACK_ALL_MASK, mUserId);
for (int i = enabledServices.size() - 1; i >= 0; i--) {
if (TextUtils.equals(defaultShortcutTarget, enabledServices.get(i).getId())) {
return;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index c009fc3b7e63..9bc6671bbc31 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
import static android.content.ContentProvider.getUriWithoutUserId;
import static android.content.ContentProvider.getUserIdFromUri;
+import static android.service.chooser.Flags.notifySingleItemChangeOnIconLoad;
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
@@ -163,9 +164,11 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -3212,6 +3215,8 @@ public class ChooserActivity extends ResolverActivity implements
private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
+ private final Set<ViewHolderBase> mBoundViewHolders = new HashSet<>();
+
ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
super();
mChooserListAdapter = wrappedAdapter;
@@ -3232,6 +3237,31 @@ public class ChooserActivity extends ResolverActivity implements
notifyDataSetChanged();
}
});
+ if (notifySingleItemChangeOnIconLoad()) {
+ wrappedAdapter.setOnIconLoadedListener(this::onTargetIconLoaded);
+ }
+ }
+
+ private void onTargetIconLoaded(DisplayResolveInfo info) {
+ for (ViewHolderBase holder : mBoundViewHolders) {
+ switch (holder.getViewType()) {
+ case VIEW_TYPE_NORMAL:
+ TargetInfo itemInfo =
+ mChooserListAdapter.getItem(
+ ((ItemViewHolder) holder).mListPosition);
+ if (info == itemInfo) {
+ notifyItemChanged(holder.getAdapterPosition());
+ }
+ break;
+ case VIEW_TYPE_CALLER_AND_RANK:
+ ItemGroupViewHolder groupHolder = (ItemGroupViewHolder) holder;
+ if (suggestedAppsGroupContainsTarget(groupHolder, info)) {
+ notifyItemChanged(holder.getAdapterPosition());
+ }
+ break;
+ }
+
+ }
}
public void setFooterHeight(int height) {
@@ -3382,6 +3412,9 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (notifySingleItemChangeOnIconLoad()) {
+ mBoundViewHolders.add((ViewHolderBase) holder);
+ }
int viewType = ((ViewHolderBase) holder).getViewType();
switch (viewType) {
case VIEW_TYPE_DIRECT_SHARE:
@@ -3396,6 +3429,22 @@ public class ChooserActivity extends ResolverActivity implements
}
@Override
+ public void onViewRecycled(RecyclerView.ViewHolder holder) {
+ if (notifySingleItemChangeOnIconLoad()) {
+ mBoundViewHolders.remove((ViewHolderBase) holder);
+ }
+ super.onViewRecycled(holder);
+ }
+
+ @Override
+ public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
+ if (notifySingleItemChangeOnIconLoad()) {
+ mBoundViewHolders.remove((ViewHolderBase) holder);
+ }
+ return super.onFailedToRecycleView(holder);
+ }
+
+ @Override
public int getItemViewType(int position) {
int count;
@@ -3604,6 +3653,33 @@ public class ChooserActivity extends ResolverActivity implements
}
}
+ /**
+ * Checks whether the suggested apps group, {@code holder}, contains the target,
+ * {@code info}.
+ */
+ private boolean suggestedAppsGroupContainsTarget(
+ ItemGroupViewHolder holder, DisplayResolveInfo info) {
+
+ int position = holder.getAdapterPosition();
+ int start = getListPosition(position);
+ int startType = getRowType(start);
+
+ int columnCount = holder.getColumnCount();
+ int end = start + columnCount - 1;
+ while (getRowType(end) != startType && end >= start) {
+ end--;
+ }
+
+ for (int i = 0; i < columnCount; i++) {
+ if (start + i <= end) {
+ if (mChooserListAdapter.getItem(holder.getItemIndex(i)) == info) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
int getListPosition(int position) {
position -= getSystemRowCount() + getProfileRowCount();
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index d38689c7505b..1b8c36db3908 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -16,9 +16,12 @@
package com.android.internal.app;
+import static android.service.chooser.Flags.notifySingleItemChangeOnIconLoad;
+
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+import android.annotation.Nullable;
import android.app.prediction.AppPredictor;
import android.content.ComponentName;
import android.content.Context;
@@ -56,6 +59,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
public class ChooserListAdapter extends ResolverListAdapter {
private static final String TAG = "ChooserListAdapter";
@@ -108,6 +112,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
// Represents the UserSpace in which the Initial Intents should be resolved.
private final UserHandle mInitialIntentsUserSpace;
+ @Nullable
+ private Consumer<DisplayResolveInfo> mOnIconLoadedListener;
+
// For pinned direct share labels, if the text spans multiple lines, the TextView will consume
// the full width, even if the characters actually take up less than that. Measure the actual
// line widths and constrain the View's width based upon that so that the pin doesn't end up
@@ -218,6 +225,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
true);
}
+ public void setOnIconLoadedListener(Consumer<DisplayResolveInfo> onIconLoadedListener) {
+ mOnIconLoadedListener = onIconLoadedListener;
+ }
+
AppPredictor getAppPredictor() {
return mAppPredictor;
}
@@ -329,6 +340,15 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
}
+ @Override
+ protected void onIconLoaded(DisplayResolveInfo info) {
+ if (notifySingleItemChangeOnIconLoad() && mOnIconLoadedListener != null) {
+ mOnIconLoadedListener.accept(info);
+ } else {
+ notifyDataSetChanged();
+ }
+ }
+
private void loadDirectShareIcon(SelectableTargetInfo info) {
LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info);
if (task == null) {
diff --git a/core/java/com/android/internal/app/MediaRouteChooserContentManager.java b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
new file mode 100644
index 000000000000..09c6f5e6caaa
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import com.android.internal.R;
+
+public class MediaRouteChooserContentManager {
+ Context mContext;
+
+ private final boolean mShowProgressBarWhenEmpty;
+
+ public MediaRouteChooserContentManager(Context context, boolean showProgressBarWhenEmpty) {
+ mContext = context;
+ mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
+ }
+
+ /**
+ * Starts binding all the views (list view, empty view, etc.) using the
+ * given container view.
+ */
+ public void bindViews(View containerView) {
+ View emptyView = containerView.findViewById(android.R.id.empty);
+ ListView listView = containerView.findViewById(R.id.media_route_list);
+ listView.setEmptyView(emptyView);
+
+ if (!mShowProgressBarWhenEmpty) {
+ containerView.findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
+
+ // Center the empty view when the progress bar is not shown.
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) emptyView.getLayoutParams();
+ params.gravity = Gravity.CENTER;
+ emptyView.setLayoutParams(params);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
index 23d966fdbc0d..5030a143ea94 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -23,14 +23,12 @@ import android.media.MediaRouter.RouteInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
@@ -52,15 +50,15 @@ import java.util.Comparator;
public class MediaRouteChooserDialog extends AlertDialog {
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
- private final boolean mShowProgressBarWhenEmpty;
private int mRouteTypes;
private View.OnClickListener mExtendedSettingsClickListener;
private RouteAdapter mAdapter;
- private ListView mListView;
private Button mExtendedSettingsButton;
private boolean mAttachedToWindow;
+ private final MediaRouteChooserContentManager mContentManager;
+
public MediaRouteChooserDialog(Context context, int theme) {
this(context, theme, true);
}
@@ -70,7 +68,7 @@ public class MediaRouteChooserDialog extends AlertDialog {
mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mCallback = new MediaRouterCallback();
- mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
+ mContentManager = new MediaRouteChooserContentManager(context, showProgressBarWhenEmpty);
}
/**
@@ -128,8 +126,9 @@ public class MediaRouteChooserDialog extends AlertDialog {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Note: setView must be called before super.onCreate().
- setView(LayoutInflater.from(getContext()).inflate(R.layout.media_route_chooser_dialog,
- null));
+ View containerView = LayoutInflater.from(getContext()).inflate(
+ R.layout.media_route_chooser_dialog, null);
+ setView(containerView);
setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
? R.string.media_route_chooser_title_for_remote_display
@@ -140,25 +139,15 @@ public class MediaRouteChooserDialog extends AlertDialog {
super.onCreate(savedInstanceState);
- View emptyView = findViewById(android.R.id.empty);
mAdapter = new RouteAdapter(getContext());
- mListView = (ListView) findViewById(R.id.media_route_list);
- mListView.setAdapter(mAdapter);
- mListView.setOnItemClickListener(mAdapter);
- mListView.setEmptyView(emptyView);
+ ListView listView = findViewById(R.id.media_route_list);
+ listView.setAdapter(mAdapter);
+ listView.setOnItemClickListener(mAdapter);
- mExtendedSettingsButton = (Button) findViewById(R.id.media_route_extended_settings_button);
+ mExtendedSettingsButton = findViewById(R.id.media_route_extended_settings_button);
updateExtendedSettingsButton();
- if (!mShowProgressBarWhenEmpty) {
- findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
-
- // Center the empty view when the progress bar is not shown.
- LinearLayout.LayoutParams params =
- (LinearLayout.LayoutParams) emptyView.getLayoutParams();
- params.gravity = Gravity.CENTER;
- emptyView.setLayoutParams(params);
- }
+ mContentManager.bindViews(containerView);
}
private void updateExtendedSettingsButton() {
@@ -240,8 +229,8 @@ public class MediaRouteChooserDialog extends AlertDialog {
view = mInflater.inflate(R.layout.media_route_list_item, parent, false);
}
MediaRouter.RouteInfo route = getItem(position);
- TextView text1 = (TextView)view.findViewById(android.R.id.text1);
- TextView text2 = (TextView)view.findViewById(android.R.id.text2);
+ TextView text1 = view.findViewById(android.R.id.text1);
+ TextView text2 = view.findViewById(android.R.id.text2);
text1.setText(route.getName());
CharSequence description = route.getDescription();
if (TextUtils.isEmpty(description)) {
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 54c0e61fd5cd..4d9ce86096c7 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -680,6 +680,10 @@ public class ResolverListAdapter extends BaseAdapter {
}
}
+ protected void onIconLoaded(DisplayResolveInfo info) {
+ notifyDataSetChanged();
+ }
+
private void loadLabel(DisplayResolveInfo info) {
LoadLabelTask task = mLabelLoaders.get(info);
if (task == null) {
@@ -1004,7 +1008,7 @@ public class ResolverListAdapter extends BaseAdapter {
mResolverListCommunicator.updateProfileViewButton();
} else if (!mDisplayResolveInfo.hasDisplayIcon()) {
mDisplayResolveInfo.setDisplayIcon(d);
- notifyDataSetChanged();
+ onIconLoaded(mDisplayResolveInfo);
}
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 81ca23173457..c6207f9451c2 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -45,11 +45,13 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -203,15 +205,6 @@ public class BatteryStatsHistory {
BatteryHistoryFragment getLatestFragment();
/**
- * Given a fragment, returns the earliest fragment that follows it whose monotonic
- * start time falls within the specified range. `startTimeMs` is inclusive, `endTimeMs`
- * is exclusive.
- */
- @Nullable
- BatteryHistoryFragment getNextFragment(BatteryHistoryFragment current, long startTimeMs,
- long endTimeMs);
-
- /**
* Acquires a lock on the entire store.
*/
void lock();
@@ -268,6 +261,60 @@ public class BatteryStatsHistory {
void reset();
}
+ class BatteryHistoryParcelContainer {
+ private boolean mParcelReadyForReading;
+ private Parcel mParcel;
+ private BatteryStatsHistory.BatteryHistoryFragment mFragment;
+ private long mMonotonicStartTime;
+
+ BatteryHistoryParcelContainer(@NonNull Parcel parcel, long monotonicStartTime) {
+ mParcel = parcel;
+ mMonotonicStartTime = monotonicStartTime;
+ mParcelReadyForReading = true;
+ }
+
+ BatteryHistoryParcelContainer(@NonNull BatteryHistoryFragment fragment) {
+ mFragment = fragment;
+ mMonotonicStartTime = fragment.monotonicTimeMs;
+ mParcelReadyForReading = false;
+ }
+
+ @Nullable
+ Parcel getParcel() {
+ if (mParcelReadyForReading) {
+ return mParcel;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ if (readFragmentToParcel(parcel, mFragment)) {
+ parcel.readInt(); // skip buffer size
+ mParcel = parcel;
+ } else {
+ parcel.recycle();
+ }
+ mParcelReadyForReading = true;
+ return mParcel;
+ }
+
+ long getMonotonicStartTime() {
+ return mMonotonicStartTime;
+ }
+
+ /**
+ * Recycles the parcel (if appropriate). Should be called after the parcel has been
+ * processed by the iterator.
+ */
+ void close() {
+ if (mParcel != null && mFragment != null) {
+ mParcel.recycle();
+ }
+ // ParcelContainers are not meant to be reusable. To prevent any unintentional
+ // access to the parcel after it has been recycled, clear the references to it.
+ mParcel = null;
+ mFragment = null;
+ }
+ }
+
private final Parcel mHistoryBuffer;
private final HistoryStepDetailsCalculator mStepDetailsCalculator;
private final Clock mClock;
@@ -447,6 +494,7 @@ public class BatteryStatsHistory {
mWritableHistory = writableHistory;
if (mWritableHistory != null) {
mMutable = false;
+ mHistoryBufferStartTime = mWritableHistory.mHistoryBufferStartTime;
mHistoryMonotonicEndTime = mWritableHistory.mHistoryMonotonicEndTime;
}
@@ -689,95 +737,66 @@ public class BatteryStatsHistory {
}
/**
- * When iterating history files and history buffer, always start from the lowest numbered
- * history file, when reached the mActiveFile (highest numbered history file), do not read from
- * mActiveFile, read from history buffer instead because the buffer has more updated data.
- *
- * @return The parcel that has next record. null if finished all history files and history
- * buffer
+ * Returns all chunks of accumulated history that contain items within the range between
+ * `startTimeMs` (inclusive) and `endTimeMs` (exclusive)
*/
- @Nullable
- public Parcel getNextParcel(long startTimeMs, long endTimeMs) {
- checkImmutable();
+ Queue<BatteryHistoryParcelContainer> getParcelContainers(long startTimeMs, long endTimeMs) {
+ if (mMutable) {
+ throw new IllegalStateException("Iterating over a mutable battery history");
+ }
- // First iterate through all records in current parcel.
- if (mCurrentParcel != null) {
- if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
- // There are more records in current parcel.
- return mCurrentParcel;
- } else if (mHistoryBuffer == mCurrentParcel) {
- // finished iterate through all history files and history buffer.
- return null;
- } else if (mHistoryParcels == null
- || !mHistoryParcels.contains(mCurrentParcel)) {
- // current parcel is from history file.
- mCurrentParcel.recycle();
- }
+ if (endTimeMs == MonotonicClock.UNDEFINED || endTimeMs == 0) {
+ endTimeMs = Long.MAX_VALUE;
}
+ Queue<BatteryHistoryParcelContainer> containers = new ArrayDeque<>();
+
if (mStore != null) {
- BatteryHistoryFragment next = mStore.getNextFragment(mCurrentFragment, startTimeMs,
- endTimeMs);
- while (next != null) {
- mCurrentParcel = null;
- mCurrentParcelEnd = 0;
- final Parcel p = Parcel.obtain();
- if (readFragmentToParcel(p, next)) {
- int bufSize = p.readInt();
- int curPos = p.dataPosition();
- mCurrentParcelEnd = curPos + bufSize;
- mCurrentParcel = p;
- if (curPos < mCurrentParcelEnd) {
- mCurrentFragment = next;
- return mCurrentParcel;
- }
- } else {
- p.recycle();
+ List<BatteryHistoryFragment> fragments = mStore.getFragments();
+ for (int i = 0; i < fragments.size(); i++) {
+ BatteryHistoryFragment fragment = fragments.get(i);
+ if (fragment.monotonicTimeMs >= endTimeMs) {
+ break;
+ }
+
+ if (fragment.monotonicTimeMs >= startTimeMs && fragment != mActiveFragment) {
+ containers.add(new BatteryHistoryParcelContainer(fragment));
}
- next = mStore.getNextFragment(next, startTimeMs, endTimeMs);
}
}
- // mHistoryParcels is created when BatteryStatsImpl object is created from deserialization
- // of a parcel, such as Settings app or checkin file.
if (mHistoryParcels != null) {
- while (mParcelIndex < mHistoryParcels.size()) {
- final Parcel p = mHistoryParcels.get(mParcelIndex++);
+ for (int i = 0; i < mHistoryParcels.size(); i++) {
+ final Parcel p = mHistoryParcels.get(i);
if (!verifyVersion(p)) {
continue;
}
- // skip monotonic time field.
- p.readLong();
- // skip monotonic end time field
- p.readLong();
+
+ long monotonicStartTime = p.readLong();
+ if (monotonicStartTime >= endTimeMs) {
+ continue;
+ }
+
+ long monotonicEndTime = p.readLong();
+ if (monotonicEndTime < startTimeMs) {
+ continue;
+ }
+
// skip monotonic size field
p.readLong();
+ // skip buffer size field
+ p.readInt();
- final int bufSize = p.readInt();
- final int curPos = p.dataPosition();
- mCurrentParcelEnd = curPos + bufSize;
- mCurrentParcel = p;
- if (curPos < mCurrentParcelEnd) {
- return mCurrentParcel;
- }
+ containers.add(new BatteryHistoryParcelContainer(p, monotonicStartTime));
}
}
- // finished iterator through history files (except the last one), now history buffer.
- if (mHistoryBuffer.dataSize() <= 0) {
- // buffer is empty.
- return null;
- }
- mHistoryBuffer.setDataPosition(0);
- mCurrentParcel = mHistoryBuffer;
- mCurrentParcelEnd = mCurrentParcel.dataSize();
- return mCurrentParcel;
- }
-
- private void checkImmutable() {
- if (mMutable) {
- throw new IllegalStateException("Iterating over a mutable battery history");
+ if (mHistoryBufferStartTime < endTimeMs) {
+ mHistoryBuffer.setDataPosition(0);
+ containers.add(
+ new BatteryHistoryParcelContainer(mHistoryBuffer, mHistoryBufferStartTime));
}
+ return containers;
}
/**
@@ -818,21 +837,6 @@ public class BatteryStatsHistory {
}
/**
- * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
- * buffer.
- */
- public long getHistoryBufferStartTime(Parcel p) {
- int pos = p.dataPosition();
- p.setDataPosition(0);
- p.readInt(); // Skip the version field
- long monotonicTime = p.readLong();
- p.readLong(); // Skip monotonic end time field
- p.readLong(); // Skip monotonic size field
- p.setDataPosition(pos);
- return monotonicTime;
- }
-
- /**
* Writes the battery history contents for persistence.
*/
public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 38398b4bf6f0..09f9b0bf8c7e 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -24,6 +24,7 @@ import android.util.Slog;
import android.util.SparseArray;
import java.util.Iterator;
+import java.util.Queue;
/**
* An iterator for {@link BatteryStats.HistoryItem}'s.
@@ -48,7 +49,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
private long mBaseMonotonicTime;
private long mBaseTimeUtc;
private int mItemIndex = 0;
- private int mMaxHistoryItems;
+ private final int mMaxHistoryItems;
+ private int mParcelDataPosition;
+
+ private Queue<BatteryStatsHistory.BatteryHistoryParcelContainer> mParcelContainers;
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
long endTimeMs) {
@@ -62,7 +66,11 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
@Override
public boolean hasNext() {
if (!mNextItemReady) {
- advance();
+ if (!advance()) {
+ mHistoryItem = null;
+ close();
+ }
+ mNextItemReady = true;
}
return mHistoryItem != null;
@@ -75,35 +83,48 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
@Override
public BatteryStats.HistoryItem next() {
if (!mNextItemReady) {
- advance();
+ if (!advance()) {
+ mHistoryItem = null;
+ close();
+ }
}
mNextItemReady = false;
return mHistoryItem;
}
- private void advance() {
- while (true) {
- if (mItemIndex > mMaxHistoryItems) {
- Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex);
- break;
- }
+ private boolean advance() {
+ if (mParcelContainers == null) {
+ mParcelContainers = mBatteryStatsHistory.getParcelContainers(mStartTimeMs, mEndTimeMs);
+ }
- Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs);
- if (p == null) {
- break;
+ BatteryStatsHistory.BatteryHistoryParcelContainer container;
+ while ((container = mParcelContainers.peek()) != null) {
+ Parcel p = container.getParcel();
+ if (p == null || p.dataPosition() >= p.dataSize()) {
+ container.close();
+ mParcelContainers.remove();
+ mParcelDataPosition = 0;
+ continue;
}
if (!mTimeInitialized) {
- mBaseMonotonicTime = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+ mBaseMonotonicTime = container.getMonotonicStartTime();
mHistoryItem.time = mBaseMonotonicTime;
mTimeInitialized = true;
}
try {
readHistoryDelta(p, mHistoryItem);
+ int dataPosition = p.dataPosition();
+ if (dataPosition <= mParcelDataPosition) {
+ Slog.wtf(TAG, "Corrupted battery history, parcel is not progressing: "
+ + dataPosition + " of " + p.dataSize());
+ return false;
+ }
+ mParcelDataPosition = dataPosition;
} catch (Throwable t) {
Slog.wtf(TAG, "Corrupted battery history", t);
- break;
+ return false;
}
if (mHistoryItem.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
@@ -111,21 +132,24 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
mBaseTimeUtc = mHistoryItem.currentTime - (mHistoryItem.time - mBaseMonotonicTime);
}
- mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime);
+ if (mHistoryItem.time < mStartTimeMs) {
+ continue;
+ }
- if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
- break;
+ if (mEndTimeMs != 0 && mEndTimeMs != MonotonicClock.UNDEFINED
+ && mHistoryItem.time >= mEndTimeMs) {
+ return false;
}
- if (mHistoryItem.time >= mStartTimeMs) {
- mItemIndex++;
- mNextItemReady = true;
- return;
+
+ if (mItemIndex++ > mMaxHistoryItems) {
+ Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex);
+ return false;
}
- }
- mHistoryItem = null;
- mNextItemReady = true;
- close();
+ mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime);
+ return true;
+ }
+ return false;
}
private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index e20a52b24485..3d81e4fc7acd 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -120,6 +120,7 @@ import com.android.internal.view.menu.MenuHelper;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
+import com.android.window.flags.Flags;
import java.util.List;
import java.util.concurrent.Executor;
@@ -1003,7 +1004,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
public void onWindowSystemUiVisibilityChanged(int visible) {
updateColorViews(null /* insets */, true /* animate */);
- if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) {
+ if (!Flags.actionModeEdgeToEdge()
+ && mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) {
updateStatusGuardColor();
}
}
@@ -1040,7 +1042,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
mFrameOffsets.set(insets.getSystemWindowInsetsAsRect());
insets = updateColorViews(insets, true /* animate */);
- insets = updateStatusGuard(insets);
+ insets = updateActionModeInsets(insets);
if (getForeground() != null) {
drawableChanged();
}
@@ -1592,7 +1594,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
}
- private WindowInsets updateStatusGuard(WindowInsets insets) {
+ private WindowInsets updateActionModeInsets(WindowInsets insets) {
boolean showStatusGuard = false;
// Show the status guard when the non-overlay contextual action bar is showing
if (mPrimaryActionModeView != null) {
@@ -1608,54 +1610,78 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
final Rect rect = mTempRect;
// Apply the insets that have not been applied by the contentParent yet.
- WindowInsets innerInsets =
+ final WindowInsets innerInsets =
mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
- int newTopMargin = innerInsets.getSystemWindowInsetTop();
- int newLeftMargin = innerInsets.getSystemWindowInsetLeft();
- int newRightMargin = innerInsets.getSystemWindowInsetRight();
-
- // Must use root window insets for the guard, because the color views consume
- // the navigation bar inset if the window does not request LAYOUT_HIDE_NAV - but
- // the status guard is attached at the root.
- WindowInsets rootInsets = getRootWindowInsets();
- int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft();
- int newGuardRightMargin = rootInsets.getSystemWindowInsetRight();
-
- if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin
- || mlp.rightMargin != newRightMargin) {
- mlpChanged = true;
- mlp.topMargin = newTopMargin;
- mlp.leftMargin = newLeftMargin;
- mlp.rightMargin = newRightMargin;
- }
+ final boolean consumesSystemWindowInsetsTop;
+ if (Flags.actionModeEdgeToEdge()) {
+ final Insets newPadding = innerInsets.getSystemWindowInsets();
+ final Insets newMargin = innerInsets.getInsets(
+ WindowInsets.Type.navigationBars());
+
+ // Don't extend into navigation bar area so the width can align with status
+ // bar color view.
+ if (mlp.leftMargin != newMargin.left
+ || mlp.rightMargin != newMargin.right) {
+ mlpChanged = true;
+ mlp.leftMargin = newMargin.left;
+ mlp.rightMargin = newMargin.right;
+ }
+
+ mPrimaryActionModeView.setPadding(
+ newPadding.left - newMargin.left,
+ newPadding.top,
+ newPadding.right - newMargin.right,
+ 0);
+ consumesSystemWindowInsetsTop = newPadding.top > 0;
+ } else {
+ int newTopMargin = innerInsets.getSystemWindowInsetTop();
+ int newLeftMargin = innerInsets.getSystemWindowInsetLeft();
+ int newRightMargin = innerInsets.getSystemWindowInsetRight();
+
+ // Must use root window insets for the guard, because the color views
+ // consume the navigation bar inset if the window does not request
+ // LAYOUT_HIDE_NAV - but the status guard is attached at the root.
+ WindowInsets rootInsets = getRootWindowInsets();
+ int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft();
+ int newGuardRightMargin = rootInsets.getSystemWindowInsetRight();
+
+ if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin
+ || mlp.rightMargin != newRightMargin) {
+ mlpChanged = true;
+ mlp.topMargin = newTopMargin;
+ mlp.leftMargin = newLeftMargin;
+ mlp.rightMargin = newRightMargin;
+ }
- if (newTopMargin > 0 && mStatusGuard == null) {
- mStatusGuard = new View(mContext);
- mStatusGuard.setVisibility(GONE);
- final LayoutParams lp = new LayoutParams(MATCH_PARENT,
- mlp.topMargin, Gravity.LEFT | Gravity.TOP);
- lp.leftMargin = newGuardLeftMargin;
- lp.rightMargin = newGuardRightMargin;
- addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp);
- } else if (mStatusGuard != null) {
- final LayoutParams lp = (LayoutParams)
- mStatusGuard.getLayoutParams();
- if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin
- || lp.rightMargin != newGuardRightMargin) {
- lp.height = mlp.topMargin;
+ if (newTopMargin > 0 && mStatusGuard == null) {
+ mStatusGuard = new View(mContext);
+ mStatusGuard.setVisibility(GONE);
+ final LayoutParams lp = new LayoutParams(MATCH_PARENT,
+ mlp.topMargin, Gravity.LEFT | Gravity.TOP);
lp.leftMargin = newGuardLeftMargin;
lp.rightMargin = newGuardRightMargin;
- mStatusGuard.setLayoutParams(lp);
+ addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp);
+ } else if (mStatusGuard != null) {
+ final LayoutParams lp = (LayoutParams)
+ mStatusGuard.getLayoutParams();
+ if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin
+ || lp.rightMargin != newGuardRightMargin) {
+ lp.height = mlp.topMargin;
+ lp.leftMargin = newGuardLeftMargin;
+ lp.rightMargin = newGuardRightMargin;
+ mStatusGuard.setLayoutParams(lp);
+ }
}
- }
- // The action mode's theme may differ from the app, so
- // always show the status guard above it if we have one.
- showStatusGuard = mStatusGuard != null;
+ // The action mode's theme may differ from the app, so
+ // always show the status guard above it if we have one.
+ showStatusGuard = mStatusGuard != null;
- if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) {
- // If it wasn't previously shown, the color may be stale
- updateStatusGuardColor();
+ if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) {
+ // If it wasn't previously shown, the color may be stale
+ updateStatusGuardColor();
+ }
+ consumesSystemWindowInsetsTop = showStatusGuard;
}
// We only need to consume the insets if the action
@@ -1664,14 +1690,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
// screen_simple_overlay_action_mode.xml).
final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
& (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
- if (nonOverlay && showStatusGuard) {
+ if (nonOverlay && consumesSystemWindowInsetsTop) {
insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, 0);
}
} else {
- // reset top margin
- if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) {
- mlpChanged = true;
- mlp.topMargin = 0;
+ if (!Flags.actionModeEdgeToEdge()) {
+ // reset top margin
+ if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) {
+ mlpChanged = true;
+ mlp.topMargin = 0;
+ }
}
}
if (mlpChanged) {
@@ -1679,7 +1707,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
}
}
- if (mStatusGuard != null) {
+ if (!Flags.actionModeEdgeToEdge() && mStatusGuard != null) {
mStatusGuard.setVisibility(showStatusGuard ? VISIBLE : GONE);
}
return insets;
@@ -2183,7 +2211,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
for (int i = getChildCount() - 1; i >= 0; i--) {
View v = getChildAt(i);
if (v != mStatusColorViewState.view && v != mNavigationColorViewState.view
- && v != mStatusGuard) {
+ && (Flags.actionModeEdgeToEdge() || v != mStatusGuard)) {
removeViewAt(i);
}
}
diff --git a/core/java/com/android/internal/policy/DesktopModeCompatUtils.java b/core/java/com/android/internal/policy/DesktopModeCompatUtils.java
new file mode 100644
index 000000000000..d7cfbdfed99c
--- /dev/null
+++ b/core/java/com/android/internal/policy/DesktopModeCompatUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
+import static android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS;
+
+import android.annotation.NonNull;
+import android.content.pm.ActivityInfo;
+import android.window.DesktopModeFlags;
+
+/**
+ * Utility functions for app compat in desktop windowing used by both window manager and System UI.
+ * @hide
+ */
+public final class DesktopModeCompatUtils {
+
+ /**
+ * Whether the caption insets should be excluded from configuration for system to handle.
+ * The caller should ensure the activity is in or entering desktop view.
+ *
+ * <p> The treatment is enabled when all the of the following is true:
+ * <li> Any flags to forcibly consume caption insets are enabled.
+ * <li> Top activity have configuration coupled with insets.
+ * <li> Task is not resizeable or per-app override
+ * {@link ActivityInfo#OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS} is enabled.
+ */
+ public static boolean shouldExcludeCaptionFromAppBounds(@NonNull ActivityInfo info,
+ boolean isResizeable, boolean optOutEdgeToEdge) {
+ return DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue()
+ && isAnyForceConsumptionFlagsEnabled()
+ && !isConfigurationDecoupled(info, optOutEdgeToEdge)
+ && (!isResizeable
+ || info.isChangeEnabled(OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS));
+ }
+
+ private static boolean isConfigurationDecoupled(@NonNull ActivityInfo info,
+ boolean optOutEdgeToEdge) {
+ return info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) && !optOutEdgeToEdge;
+ }
+
+ private static boolean isAnyForceConsumptionFlagsEnabled() {
+ return DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue()
+ || DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue();
+ }
+}
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index 4ed15faf8b89..783c68695fb3 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -16,6 +16,8 @@
package com.android.internal.policy;
+import android.annotation.DimenRes;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
@@ -99,4 +101,19 @@ public final class SystemBarUtils {
public static int getTaskbarHeight(Resources res) {
return res.getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
+
+ /**
+ * Gets the default app header height in desktop view in pixels.
+ */
+ public static int getDesktopViewAppHeaderHeightPx(@NonNull Context context) {
+ return context.getResources().getDimensionPixelSize(getDesktopViewAppHeaderHeightId());
+ }
+
+ /**
+ * Gets the dimen resource id of the default app header height in desktop view.
+ */
+ @DimenRes
+ public static int getDesktopViewAppHeaderHeightId() {
+ return R.dimen.desktop_view_default_header_height;
+ }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 2d989943800e..6f7e5ad51b89 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -79,6 +79,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -864,5 +865,24 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
throw new RuntimeException("Both mMessageString and mMessageHash should never be null");
}
}
+
+ /**
+ * This is only used by unit tests to wait until {@link #connectToConfigurationService} is
+ * done. Because unit tests are sensitive to concurrent accesses.
+ */
+ @VisibleForTesting
+ public static void waitForInitialization() {
+ final IProtoLog currentInstance = ProtoLog.getSingleInstance();
+ if (!(currentInstance instanceof PerfettoProtoLogImpl protoLog)) {
+ return;
+ }
+ try {
+ protoLog.mBackgroundLoggingService.submit(() -> {
+ Log.i(LOG_TAG, "Complete initialization");
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(LOG_TAG, "Failed to wait for tracing service", e);
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9380d99b7de3..0791612fa0e8 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -105,7 +105,7 @@ interface IInputMethodManager {
in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, int userId,
- in ImeOnBackInvokedDispatcher imeDispatcher);
+ in ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible);
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
@@ -120,8 +120,8 @@ interface IInputMethodManager {
in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, int userId,
- in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod);
+ in ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
@@ -224,7 +224,7 @@ interface IInputMethodManager {
* async **/
oneway void acceptStylusHandwritingDelegationAsync(in IInputMethodClient client, in int userId,
in String delegatePackageName, in String delegatorPackageName, int flags,
- in IBooleanListener callback);
+ in IBooleanListener callback);
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 80fc218839d5..d5bb51187ba4 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -34,6 +34,7 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
+import com.android.window.flags.Flags;
/**
* @hide
@@ -315,12 +316,14 @@ public class ActionBarContextView extends AbsActionBarView {
final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
- int maxHeight = mContentHeight > 0 ?
- mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
+ final int maxHeight = !Flags.actionModeEdgeToEdge() && mContentHeight > 0
+ ? mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
final int verticalPadding = getPaddingTop() + getPaddingBottom();
int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
- final int height = maxHeight - verticalPadding;
+ final int height = Flags.actionModeEdgeToEdge()
+ ? mContentHeight > 0 ? mContentHeight : maxHeight
+ : maxHeight - verticalPadding;
final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
if (mClose != null) {
@@ -376,7 +379,8 @@ public class ActionBarContextView extends AbsActionBarView {
}
setMeasuredDimension(contentWidth, measuredHeight);
} else {
- setMeasuredDimension(contentWidth, maxHeight);
+ setMeasuredDimension(contentWidth, Flags.actionModeEdgeToEdge()
+ ? mContentHeight + verticalPadding : maxHeight);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index ff57fd4fe2ce..362b79db4003 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -294,54 +294,24 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
}
}
- private boolean applyInsets(View view, Rect insets, boolean toPadding,
- boolean left, boolean top, boolean right, boolean bottom) {
- boolean changed;
- if (toPadding) {
- changed = setMargin(view, EMPTY_RECT, left, top, right, bottom);
- changed |= setPadding(view, insets, left, top, right, bottom);
- } else {
- changed = setPadding(view, EMPTY_RECT, left, top, right, bottom);
- changed |= setMargin(view, insets, left, top, right, bottom);
- }
- return changed;
- }
-
- private boolean setPadding(View view, Rect insets,
- boolean left, boolean top, boolean right, boolean bottom) {
- if ((left && view.getPaddingLeft() != insets.left)
- || (top && view.getPaddingTop() != insets.top)
- || (right && view.getPaddingRight() != insets.right)
- || (bottom && view.getPaddingBottom() != insets.bottom)) {
- view.setPadding(
- left ? insets.left : view.getPaddingLeft(),
- top ? insets.top : view.getPaddingTop(),
- right ? insets.right : view.getPaddingRight(),
- bottom ? insets.bottom : view.getPaddingBottom());
- return true;
- }
- return false;
- }
-
- private boolean setMargin(View view, Rect insets,
- boolean left, boolean top, boolean right, boolean bottom) {
+ private boolean setMargin(View view, int left, int top, int right, int bottom) {
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
boolean changed = false;
- if (left && lp.leftMargin != insets.left) {
+ if (lp.leftMargin != left) {
changed = true;
- lp.leftMargin = insets.left;
+ lp.leftMargin = left;
}
- if (top && lp.topMargin != insets.top) {
+ if (lp.topMargin != top) {
changed = true;
- lp.topMargin = insets.top;
+ lp.topMargin = top;
}
- if (right && lp.rightMargin != insets.right) {
+ if (lp.rightMargin != right) {
changed = true;
- lp.rightMargin = insets.right;
+ lp.rightMargin = right;
}
- if (bottom && lp.bottomMargin != insets.bottom) {
+ if (lp.bottomMargin != bottom) {
changed = true;
- lp.bottomMargin = insets.bottom;
+ lp.bottomMargin = bottom;
}
return changed;
}
@@ -367,12 +337,30 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
final Insets sysInsets = insets.getSystemWindowInsets();
mSystemInsets.set(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom);
- // The top and bottom action bars are always within the content area.
- boolean changed = applyInsets(mActionBarTop, mSystemInsets,
- mActionBarExtendsIntoSystemInsets, true, true, true, false);
- if (mActionBarBottom != null) {
- changed |= applyInsets(mActionBarBottom, mSystemInsets,
- mActionBarExtendsIntoSystemInsets, true, false, true, true);
+ boolean changed = false;
+ if (mActionBarExtendsIntoSystemInsets) {
+ // Don't extend into navigation bar area so the width can align with status bar
+ // color view.
+ final Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ final int paddingLeft = sysInsets.left - navBarInsets.left;
+ final int paddingRight = sysInsets.right - navBarInsets.right;
+ mActionBarTop.setPadding(paddingLeft, sysInsets.top, paddingRight, 0);
+ changed |= setMargin(
+ mActionBarTop, navBarInsets.left, 0, navBarInsets.right, 0);
+ if (mActionBarBottom != null) {
+ mActionBarBottom.setPadding(paddingLeft, 0, paddingRight, sysInsets.bottom);
+ changed |= setMargin(
+ mActionBarBottom, navBarInsets.left, 0, navBarInsets.right, 0);
+ }
+ } else {
+ mActionBarTop.setPadding(0, 0, 0, 0);
+ changed |= setMargin(
+ mActionBarTop, sysInsets.left, sysInsets.top, sysInsets.right, 0);
+ if (mActionBarBottom != null) {
+ mActionBarBottom.setPadding(0, 0, 0, 0);
+ changed |= setMargin(
+ mActionBarTop, sysInsets.left, 0, sysInsets.right, sysInsets.bottom);
+ }
}
// Cannot use the result of computeSystemWindowInsets, because that consumes the
@@ -521,7 +509,12 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
);
}
}
- setMargin(mContent, mContentInsets, true, true, true, true);
+ setMargin(
+ mContent,
+ mContentInsets.left,
+ mContentInsets.top,
+ mContentInsets.right,
+ mContentInsets.bottom);
if (!mLastInnerInsets.equals(mInnerInsets)) {
// If the inner insets have changed, we need to dispatch this down to
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 2cca3dbc4f2f..3da19220248b 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -1178,6 +1178,7 @@ public class ConversationLayout extends FrameLayout
}
newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed);
newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
+ newGroup.setIsCollapsed(mIsCollapsed);
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
mGroups.add(newGroup);
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 1f907602cb9b..dd31c388c973 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -634,7 +634,6 @@ public class LockPatternView extends View {
}
private void notifyPatternStarted() {
- sendAccessEvent(R.string.lockscreen_access_pattern_start);
if (mOnPatternListener != null) {
mOnPatternListener.onPatternStart();
}
@@ -642,14 +641,12 @@ public class LockPatternView extends View {
@UnsupportedAppUsage
private void notifyPatternDetected() {
- sendAccessEvent(R.string.lockscreen_access_pattern_detected);
if (mOnPatternListener != null) {
mOnPatternListener.onPatternDetected(mPattern);
}
}
private void notifyPatternCleared() {
- sendAccessEvent(R.string.lockscreen_access_pattern_cleared);
if (mOnPatternListener != null) {
mOnPatternListener.onPatternCleared();
}
@@ -1240,10 +1237,6 @@ public class LockPatternView extends View {
}
}
- private void sendAccessEvent(int resId) {
- announceForAccessibility(mContext.getString(resId));
- }
-
private void handleActionUp() {
// report pattern detected
if (!mPattern.isEmpty()) {
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index b31a200218ee..21bb7a9468bd 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -103,6 +103,7 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
private boolean mShowingAvatar = true;
private CharSequence mSenderName;
private boolean mSingleLine = false;
+ private boolean mIsCollapsed = false;
private LinearLayout mContentContainer;
private int mRequestedMaxDisplayedLines = Integer.MAX_VALUE;
private int mSenderTextPaddingSingleLine;
@@ -451,7 +452,7 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
private void updateIconVisibility() {
if (Flags.notificationsRedesignTemplates()) {
// We don't show any icon (other than the app or person icon) in the collapsed form.
- mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE);
+ mMessagingIconContainer.setVisibility(mIsCollapsed ? GONE : VISIBLE);
}
}
@@ -714,10 +715,18 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
updateMaxDisplayedLines();
updateClipRect();
updateSenderVisibility();
- updateIconVisibility();
}
}
+ /**
+ * Sets whether this is in a collapsed layout or not. Certain elements like icons are not shown
+ * when the notification is collapsed.
+ */
+ public void setIsCollapsed(boolean isCollapsed) {
+ mIsCollapsed = isCollapsed;
+ updateIconVisibility();
+ }
+
public boolean isSingleLine() {
return mSingleLine;
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9fe2de82adee..4cc4b38f95a5 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -551,6 +551,7 @@ public class MessagingLayout extends FrameLayout
}
newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
newGroup.setShowingAvatar(!mIsCollapsed);
+ newGroup.setIsCollapsed(mIsCollapsed);
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
mGroups.add(newGroup);
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index cac2024f548d..cf9f1c8b380a 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import static android.app.Flags.notificationsRedesignTemplates;
import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
@@ -368,12 +369,17 @@ public class NotificationActionListLayout extends LinearLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mDefaultPaddingBottom = getPaddingBottom();
- mDefaultPaddingTop = getPaddingTop();
- updateHeights();
+ if (!notificationsRedesignTemplates()) {
+ mDefaultPaddingBottom = getPaddingBottom();
+ mDefaultPaddingTop = getPaddingTop();
+ updateHeights();
+ }
}
private void updateHeights() {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
int inset = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.button_inset_vertical_material);
mEmphasizedPaddingTop = getResources().getDimensionPixelSize(
@@ -440,6 +446,9 @@ public class NotificationActionListLayout extends LinearLayout {
*/
@RemotableViewMethod
public void setEmphasizedMode(boolean emphasizedMode) {
+ if (notificationsRedesignTemplates()) {
+ return;
+ }
mEmphasizedMode = emphasizedMode;
int height;
if (emphasizedMode) {
@@ -462,7 +471,9 @@ public class NotificationActionListLayout extends LinearLayout {
}
public int getExtraMeasureHeight() {
- if (mEmphasizedMode) {
+ // Note: the emphasized height is no longer different from the regular height when the
+ // notificationsRedesignTemplates flag is on.
+ if (!notificationsRedesignTemplates() && mEmphasizedMode) {
return mEmphasizedHeight - mRegularHeight;
}
return 0;
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index dd12f69a56fb..42b1bdbc51b2 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -129,6 +129,16 @@ public class NotificationExpandButton extends FrameLayout {
updateExpandedState();
}
+ /**
+ * Adjust the padding at the start of the view based on the layout direction (RTL/LTR).
+ * This is needed because RemoteViews don't have an equivalent for
+ * {@link this#setPaddingRelative}.
+ */
+ @RemotableViewMethod
+ public void setStartPadding(int startPadding) {
+ setPaddingRelative(startPadding, getPaddingTop(), getPaddingEnd(), getPaddingBottom());
+ }
+
private void updateExpandedState() {
int drawableId;
int contentDescriptionId;
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
index 3f5b4a0d61fe..3aa4c8498747 100644
--- a/core/java/com/android/internal/widget/PeopleHelper.java
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -110,7 +110,7 @@ public class PeopleHelper {
@NonNull
public Icon createAvatarSymbol(@NonNull CharSequence name, @NonNull String symbol,
@ColorInt int layoutColor) {
- if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol)
+ if (symbol == null || symbol.isEmpty() || TextUtils.isDigitsOnly(symbol)
|| SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
Icon avatarIcon = Icon.createWithResource(mContext, R.drawable.messaging_user);
avatarIcon.setTint(findColor(name, layoutColor));
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
index d3a496db2ca9..f70f4cbceb70 100644
--- a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
@@ -179,7 +179,7 @@ public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibi
* @return
*/
public boolean performClick(Component component) {
- mDocument.performClick(mRemoteContext, component.getComponentId());
+ mDocument.performClick(mRemoteContext, component.getComponentId(), "");
return true;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 796ea8d1538f..caf19e1ed34a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -19,7 +19,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
+import com.android.internal.widget.remotecompose.core.operations.DrawContent;
+import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
import com.android.internal.widget.remotecompose.core.operations.Header;
import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
@@ -28,9 +31,12 @@ import com.android.internal.widget.remotecompose.core.operations.RootContentBeha
import com.android.internal.widget.remotecompose.core.operations.ShaderData;
import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.CanvasOperations;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.Container;
import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
@@ -39,6 +45,8 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.IntMa
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
+import com.android.internal.widget.remotecompose.core.types.LongConstant;
import java.util.ArrayList;
import java.util.HashMap;
@@ -65,7 +73,7 @@ public class CoreDocument implements Serializable {
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.3f;
+ static final float BUILD = 0.5f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@@ -102,6 +110,8 @@ public class CoreDocument implements Serializable {
private IntMap<Object> mDocProperties;
+ boolean mFirstPaint = true;
+
/** Returns a version number that is monotonically increasing. */
public static int getDocumentApiLevel() {
return DOCUMENT_API_LEVEL;
@@ -437,6 +447,94 @@ public class CoreDocument implements Serializable {
return mDocProperties.get(key);
}
+ /**
+ * Apply a collection of operations to the document
+ *
+ * @param delta the delta to apply
+ */
+ public void applyUpdate(CoreDocument delta) {
+ HashMap<Integer, TextData> txtData = new HashMap<Integer, TextData>();
+ HashMap<Integer, BitmapData> imgData = new HashMap<Integer, BitmapData>();
+ HashMap<Integer, FloatConstant> fltData = new HashMap<Integer, FloatConstant>();
+ HashMap<Integer, IntegerConstant> intData = new HashMap<Integer, IntegerConstant>();
+ HashMap<Integer, LongConstant> longData = new HashMap<Integer, LongConstant>();
+ recursiveTreverse(
+ mOperations,
+ (op) -> {
+ if (op instanceof TextData) {
+ TextData d = (TextData) op;
+ txtData.put(d.mTextId, d);
+ } else if (op instanceof BitmapData) {
+ BitmapData d = (BitmapData) op;
+ imgData.put(d.mImageId, d);
+ } else if (op instanceof FloatConstant) {
+ FloatConstant d = (FloatConstant) op;
+ fltData.put(d.mId, d);
+ } else if (op instanceof IntegerConstant) {
+ IntegerConstant d = (IntegerConstant) op;
+ intData.put(d.mId, d);
+ } else if (op instanceof LongConstant) {
+ LongConstant d = (LongConstant) op;
+ longData.put(d.mId, d);
+ }
+ });
+
+ recursiveTreverse(
+ delta.mOperations,
+ (op) -> {
+ if (op instanceof TextData) {
+ TextData t = (TextData) op;
+ TextData txtInDoc = txtData.get(t.mTextId);
+ if (txtInDoc != null) {
+ txtInDoc.update(t);
+ Utils.log("update" + t.mText);
+ txtInDoc.markDirty();
+ }
+ } else if (op instanceof BitmapData) {
+ BitmapData b = (BitmapData) op;
+ BitmapData imgInDoc = imgData.get(b.mImageId);
+ if (imgInDoc != null) {
+ imgInDoc.update(b);
+ imgInDoc.markDirty();
+ }
+ } else if (op instanceof FloatConstant) {
+ FloatConstant f = (FloatConstant) op;
+ FloatConstant fltInDoc = fltData.get(f.mId);
+ if (fltInDoc != null) {
+ fltInDoc.update(f);
+ fltInDoc.markDirty();
+ }
+ } else if (op instanceof IntegerConstant) {
+ IntegerConstant ic = (IntegerConstant) op;
+ IntegerConstant intInDoc = intData.get(ic.mId);
+ if (intInDoc != null) {
+ intInDoc.update(ic);
+ intInDoc.markDirty();
+ }
+ } else if (op instanceof LongConstant) {
+ LongConstant lc = (LongConstant) op;
+ LongConstant longInDoc = longData.get(lc.mId);
+ if (longInDoc != null) {
+ longInDoc.update(lc);
+ longInDoc.markDirty();
+ }
+ }
+ });
+ }
+
+ private interface Visitor {
+ void visit(Operation op);
+ }
+
+ private void recursiveTreverse(ArrayList<Operation> mOperations, Visitor visitor) {
+ for (Operation op : mOperations) {
+ if (op instanceof Container) {
+ recursiveTreverse(((Component) op).mList, visitor);
+ }
+ visitor.visit(op);
+ }
+ }
+
// ============== Haptic support ==================
public interface HapticEngine {
/**
@@ -678,6 +776,7 @@ public class CoreDocument implements Serializable {
ArrayList<Operation> ops = finalOperationsList;
ArrayList<Container> containers = new ArrayList<>();
+ LayoutComponent lastLayoutComponent = null;
mLastId = -1;
for (Operation o : operations) {
@@ -697,6 +796,9 @@ public class CoreDocument implements Serializable {
if (component.getComponentId() < mLastId) {
mLastId = component.getComponentId();
}
+ if (component instanceof LayoutComponent) {
+ lastLayoutComponent = (LayoutComponent) component;
+ }
}
containers.add(container);
ops = container.getList();
@@ -723,7 +825,13 @@ public class CoreDocument implements Serializable {
}
ops.add((Operation) container);
}
+ if (container instanceof CanvasOperations) {
+ ((CanvasOperations) container).setComponent(lastLayoutComponent);
+ }
} else {
+ if (o instanceof DrawContent) {
+ ((DrawContent) o).setComponent(lastLayoutComponent);
+ }
ops.add(o);
}
}
@@ -784,6 +892,7 @@ public class CoreDocument implements Serializable {
registerVariables(context, mOperations);
context.mMode = RemoteContext.ContextMode.UNSET;
+ mFirstPaint = true;
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -895,7 +1004,7 @@ public class CoreDocument implements Serializable {
*
* @param id the click area id
*/
- public void performClick(@NonNull RemoteContext context, int id) {
+ public void performClick(@NonNull RemoteContext context, int id, @NonNull String metadata) {
for (ClickAreaRepresentation clickArea : mClickAreas) {
if (clickArea.mId == id) {
warnClickListeners(clickArea);
@@ -904,7 +1013,7 @@ public class CoreDocument implements Serializable {
}
for (IdActionCallback listener : mIdActionListeners) {
- listener.onAction(id, "");
+ listener.onAction(id, metadata);
}
Component component = getComponent(id);
@@ -1093,6 +1202,28 @@ public class CoreDocument implements Serializable {
}
/**
+ * Traverse the list of operations to update the variables. TODO: this should walk the
+ * dependency tree instead
+ *
+ * @param context
+ * @param operations
+ */
+ private void updateVariables(
+ @NonNull RemoteContext context, int theme, List<Operation> operations) {
+ for (int i = 0; i < operations.size(); i++) {
+ Operation op = operations.get(i);
+ if (op.isDirty() && op instanceof VariableSupport) {
+ ((VariableSupport) op).updateVariables(context);
+ op.apply(context);
+ op.markNotDirty();
+ }
+ if (op instanceof Container) {
+ updateVariables(context, theme, ((Container) op).getList());
+ }
+ }
+ }
+
+ /**
* Paint the document
*
* @param context the provided PaintContext
@@ -1110,6 +1241,13 @@ public class CoreDocument implements Serializable {
context.mRemoteComposeState = mRemoteComposeState;
context.mRemoteComposeState.setContext(context);
+ // Update any dirty variables
+ if (mFirstPaint) {
+ mFirstPaint = false;
+ } else {
+ updateVariables(context, theme, mOperations);
+ }
+
// If we have a content sizing set, we are going to take the original document
// dimension into account and apply scale+translate according to the RootContentBehavior
// rules.
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 9cbafab07a43..ac9f98bd6b15 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -25,6 +25,7 @@ import com.android.internal.widget.remotecompose.core.operations.ClipRect;
import com.android.internal.widget.remotecompose.core.operations.ColorConstant;
import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
+import com.android.internal.widget.remotecompose.core.operations.ConditionalOperations;
import com.android.internal.widget.remotecompose.core.operations.DataListFloat;
import com.android.internal.widget.remotecompose.core.operations.DataListIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
@@ -50,6 +51,7 @@ import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
import com.android.internal.widget.remotecompose.core.operations.FloatFunctionCall;
import com.android.internal.widget.remotecompose.core.operations.FloatFunctionDefine;
+import com.android.internal.widget.remotecompose.core.operations.HapticFeedback;
import com.android.internal.widget.remotecompose.core.operations.Header;
import com.android.internal.widget.remotecompose.core.operations.ImageAttribute;
import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
@@ -64,6 +66,7 @@ import com.android.internal.widget.remotecompose.core.operations.PaintData;
import com.android.internal.widget.remotecompose.core.operations.ParticlesCreate;
import com.android.internal.widget.remotecompose.core.operations.ParticlesLoop;
import com.android.internal.widget.remotecompose.core.operations.PathAppend;
+import com.android.internal.widget.remotecompose.core.operations.PathCombine;
import com.android.internal.widget.remotecompose.core.operations.PathCreate;
import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.PathTween;
@@ -101,6 +104,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.ImageLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -112,6 +116,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionMetadataOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
@@ -222,6 +227,9 @@ public class Operations {
public static final int ATTRIBUTE_TIME = 172;
public static final int CANVAS_OPERATIONS = 173;
public static final int MODIFIER_DRAW_CONTENT = 174;
+ public static final int PATH_COMBINE = 175;
+ public static final int HAPTIC_FEEDBACK = 177;
+ public static final int CONDITIONAL_OPERATIONS = 178;
///////////////////////////////////////// ======================
@@ -241,6 +249,7 @@ public class Operations {
public static final int LAYOUT_CANVAS_CONTENT = 207;
public static final int LAYOUT_TEXT = 208;
public static final int LAYOUT_STATE = 217;
+ public static final int LAYOUT_IMAGE = 234;
public static final int COMPONENT_START = 2;
@@ -272,6 +281,7 @@ public class Operations {
public static final int MODIFIER_VISIBILITY = 211;
public static final int HOST_ACTION = 209;
+ public static final int HOST_METADATA_ACTION = 216;
public static final int HOST_NAMED_ACTION = 210;
public static final int VALUE_INTEGER_CHANGE_ACTION = 212;
@@ -379,6 +389,7 @@ public class Operations {
map.put(CONTAINER_END, ContainerEnd::read);
map.put(HOST_ACTION, HostActionOperation::read);
+ map.put(HOST_METADATA_ACTION, HostActionMetadataOperation::read);
map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read);
map.put(VALUE_INTEGER_CHANGE_ACTION, ValueIntegerChangeActionOperation::read);
map.put(
@@ -401,7 +412,7 @@ public class Operations {
map.put(LAYOUT_CANVAS, CanvasLayout::read);
map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read);
map.put(LAYOUT_TEXT, TextLayout::read);
-
+ map.put(LAYOUT_IMAGE, ImageLayout::read);
map.put(LAYOUT_STATE, StateLayout::read);
map.put(DRAW_CONTENT, DrawContent::read);
@@ -426,6 +437,9 @@ public class Operations {
map.put(ATTRIBUTE_IMAGE, ImageAttribute::read);
map.put(ATTRIBUTE_TEXT, TextAttribute::read);
map.put(ATTRIBUTE_TIME, TimeAttribute::read);
+ map.put(PATH_COMBINE, PathCombine::read);
+ map.put(HAPTIC_FEEDBACK, HapticFeedback::read);
+ map.put(CONDITIONAL_OPERATIONS, ConditionalOperations::read);
// map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 1b0b9d783dff..e61ca4c1ee13 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -340,6 +340,17 @@ public abstract class PaintContext {
public abstract void tweenPath(int out, int path1, int path2, float tween);
/**
+ * Perform a between two path and return the resulting path
+ *
+ * @param out the interpolated path
+ * @param path1 start path
+ * @param path2 end path
+ * @param operation 0 = difference , 1 = intersection, 2 = reverse_difference, 3 = union, 4 =
+ * xor
+ */
+ public abstract void combinePath(int out, int path1, int path2, byte operation);
+
+ /**
* This applies changes to the current paint
*
* @param mPaintData the list of changes
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index c249adf5bd58..1f026687680f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.operations.ClipRect;
import com.android.internal.widget.remotecompose.core.operations.ColorConstant;
import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
+import com.android.internal.widget.remotecompose.core.operations.ConditionalOperations;
import com.android.internal.widget.remotecompose.core.operations.DataListFloat;
import com.android.internal.widget.remotecompose.core.operations.DataListIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
@@ -53,6 +54,7 @@ import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
import com.android.internal.widget.remotecompose.core.operations.FloatFunctionCall;
import com.android.internal.widget.remotecompose.core.operations.FloatFunctionDefine;
+import com.android.internal.widget.remotecompose.core.operations.HapticFeedback;
import com.android.internal.widget.remotecompose.core.operations.Header;
import com.android.internal.widget.remotecompose.core.operations.ImageAttribute;
import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
@@ -67,6 +69,7 @@ import com.android.internal.widget.remotecompose.core.operations.PaintData;
import com.android.internal.widget.remotecompose.core.operations.ParticlesCreate;
import com.android.internal.widget.remotecompose.core.operations.ParticlesLoop;
import com.android.internal.widget.remotecompose.core.operations.PathAppend;
+import com.android.internal.widget.remotecompose.core.operations.PathCombine;
import com.android.internal.widget.remotecompose.core.operations.PathCreate;
import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.PathTween;
@@ -99,6 +102,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.ImageLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -890,7 +894,7 @@ public class RemoteComposeBuffer {
* @return new id that merges the two text
*/
public int textMerge(int id1, int id2) {
- int textId = addText(id1 + "+" + id2);
+ int textId = nextId();
TextMerge.apply(mBuffer, textId, id1, id2);
return textId;
}
@@ -2091,6 +2095,19 @@ public class RemoteComposeBuffer {
}
/**
+ * Add an imagelayout command
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param bitmapId bitmap id
+ */
+ public void addImage(
+ int componentId, int animationId, int bitmapId, int scaleType, float alpha) {
+ mLastComponentId = getComponentId(componentId);
+ ImageLayout.apply(mBuffer, componentId, animationId, bitmapId, scaleType, alpha);
+ }
+
+ /**
* Add a row start tag
*
* @param componentId component id
@@ -2273,8 +2290,6 @@ public class RemoteComposeBuffer {
return mRemoteComposeState.nextId();
}
- private boolean mInImpulseProcess = false;
-
/**
* add an impulse. (must be followed by impulse end)
*
@@ -2283,22 +2298,16 @@ public class RemoteComposeBuffer {
*/
public void addImpulse(float duration, float start) {
ImpulseOperation.apply(mBuffer, duration, start);
- mInImpulseProcess = false;
}
/** add an impulse process */
public void addImpulseProcess() {
ImpulseProcess.apply(mBuffer);
- mInImpulseProcess = true;
}
/** Add an impulse end */
public void addImpulseEnd() {
ContainerEnd.apply(mBuffer);
- if (mInImpulseProcess) {
- ContainerEnd.apply(mBuffer);
- }
- mInImpulseProcess = false;
}
/**
@@ -2422,4 +2431,37 @@ public class RemoteComposeBuffer {
return imageId;
}
+
+ /**
+ * Combine two paths
+ *
+ * @param id output id
+ * @param path1 first path
+ * @param path2 second path
+ * @param op operation to perform OP_DIFFERENCE, OP_INTERSECT, OP_REVERSE_DIFFERENCE, OP_UNION,
+ * OP_XOR
+ */
+ public void pathCombine(int id, int path1, int path2, byte op) {
+ PathCombine.apply(mBuffer, id, path1, path2, op);
+ }
+
+ /**
+ * Perform a haptic feedback
+ *
+ * @param feedbackConstant
+ */
+ public void performHaptic(int feedbackConstant) {
+ HapticFeedback.apply(mBuffer, feedbackConstant);
+ }
+
+ /**
+ * Add a conditional operation
+ *
+ * @param type type of comparison
+ * @param a first value
+ * @param b second value
+ */
+ public void addConditionalOperations(byte type, float a, float b) {
+ ConditionalOperations.apply(mBuffer, type, a, b);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index c6b17e4116d6..e37833f33fa5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -552,6 +552,14 @@ public abstract class RemoteContext {
public abstract int getInteger(int id);
/**
+ * Get a Long given an id
+ *
+ * @param id of the long
+ * @return the value
+ */
+ public abstract long getLong(int id);
+
+ /**
* Get the color given and ID
*
* @param id of the color
@@ -629,6 +637,8 @@ public abstract class RemoteContext {
/** The delta between current and last Frame */
public static final int ID_ANIMATION_DELTA_TIME = 31;
+ public static final int ID_EPOCH_SECOND = 32;
+
public static final float FLOAT_DENSITY = Utils.asNan(ID_DENSITY);
/** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */
@@ -714,6 +724,9 @@ public abstract class RemoteContext {
/** When was this player built */
public static final float FLOAT_API_LEVEL = Utils.asNan(ID_API_LEVEL);
+ /** The time in seconds since the epoch. */
+ public static final long INT_EPOCH_SECOND = ((long) ID_EPOCH_SECOND) + 0x100000000L;
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index cd5b202f5a79..57300364e0b5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -49,9 +49,12 @@ public class TimeVariables {
OffsetDateTime offsetDateTime = dateTime.atZone(zoneId).toOffsetDateTime();
ZoneOffset offset = offsetDateTime.getOffset();
+ long epochSec = dateTime.toEpochSecond(offset);
context.loadFloat(RemoteContext.ID_OFFSET_TO_UTC, offset.getTotalSeconds());
context.loadFloat(RemoteContext.ID_CONTINUOUS_SEC, sec);
+ // This will overflow in 2038.
+ context.loadInteger(RemoteContext.ID_EPOCH_SECOND, (int) epochSec);
context.loadFloat(RemoteContext.ID_TIME_IN_SEC, currentSeconds);
context.loadFloat(RemoteContext.ID_TIME_IN_MIN, currentMinute);
context.loadFloat(RemoteContext.ID_TIME_IN_HR, hour);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 255d7a46e49e..90929e06ecd0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -41,12 +41,12 @@ import java.util.List;
public class BitmapData extends Operation implements SerializableToString, Serializable {
private static final int OP_CODE = Operations.DATA_BITMAP;
private static final String CLASS_NAME = "BitmapData";
- int mImageId;
+ public final int mImageId;
int mImageWidth;
int mImageHeight;
short mType;
short mEncoding;
- @NonNull final byte[] mBitmap;
+ @NonNull byte[] mBitmap;
/** The max size of width or height */
public static final int MAX_IMAGE_DIMENSION = 8000;
@@ -91,6 +91,19 @@ public class BitmapData extends Operation implements SerializableToString, Seria
}
/**
+ * Update the bitmap data
+ *
+ * @param from the bitmap to copy
+ */
+ public void update(BitmapData from) {
+ this.mImageWidth = from.mImageWidth;
+ this.mImageHeight = from.mImageHeight;
+ this.mBitmap = from.mBitmap;
+ this.mType = from.mType;
+ this.mEncoding = from.mEncoding;
+ }
+
+ /**
* The width of the image
*
* @return the width
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java
new file mode 100644
index 000000000000..da6035e1e87e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.widget.remotecompose.core.operations;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Container;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Represents conditional execution of a block of commands */
+public class ConditionalOperations extends PaintOperation
+ implements Container, VariableSupport, Serializable {
+ private static final String CLASS_NAME = "ConditionalOperations";
+
+ private static final int OP_CODE = Operations.CONDITIONAL_OPERATIONS;
+
+ @NonNull public ArrayList<Operation> mList = new ArrayList<>();
+
+ int mIndexVariableId;
+ byte mType;
+ float mVarA;
+ float mVarB;
+ float mVarAOut;
+ float mVarBOut;
+
+ /** Equality comparison */
+ public static final byte TYPE_EQ = 0;
+
+ /** Not equal comparison */
+ public static final byte TYPE_NEQ = 1;
+
+ /** Less than comparison */
+ public static final byte TYPE_LT = 2;
+
+ /** Less than or equal comparison */
+ public static final byte TYPE_LTE = 3;
+
+ /** Greater than comparison */
+ public static final byte TYPE_GT = 4;
+
+ /** Greater than or equal comparison */
+ public static final byte TYPE_GTE = 5;
+
+ private static final String[] TYPE_STR = {"EQ", "NEQ", "LT", "LTE", "GT", "GTE"};
+
+ @Override
+ public void registerListening(RemoteContext context) {
+ if (Float.isNaN(mVarA)) {
+ context.listensTo(Utils.idFromNan(mVarA), this);
+ }
+ if (Float.isNaN(mVarB)) {
+ context.listensTo(Utils.idFromNan(mVarB), this);
+ }
+ }
+
+ @Override
+ public void updateVariables(RemoteContext context) {
+ mVarAOut = Float.isNaN(mVarA) ? context.getFloat(Utils.idFromNan(mVarA)) : mVarA;
+ mVarBOut = Float.isNaN(mVarB) ? context.getFloat(Utils.idFromNan(mVarB)) : mVarB;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param type type of comparison
+ * @param a first value
+ * @param b second value
+ */
+ public ConditionalOperations(byte type, float a, float b) {
+ mType = type;
+ mVarAOut = mVarA = a;
+ mVarBOut = mVarB = b;
+ }
+
+ @Override
+ @NonNull
+ public ArrayList<Operation> getList() {
+ return mList;
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mType, mVarA, mVarB);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder builder =
+ new StringBuilder(
+ CLASS_NAME
+ + " "
+ + TYPE_STR[mType]
+ + "("
+ + Utils.idFromNan(mVarA)
+ + ","
+ + Utils.idFromNan(mVarB)
+ + ")\n");
+ for (Operation operation : mList) {
+ builder.append(" ");
+ builder.append(operation);
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {
+ RemoteContext remoteContext = context.getContext();
+ boolean run = false;
+ switch (mType) {
+ case TYPE_EQ:
+ run = mVarAOut == mVarBOut;
+ break;
+ case TYPE_NEQ:
+ run = mVarAOut != mVarBOut;
+ break;
+ case TYPE_LT:
+ run = mVarAOut < mVarBOut;
+ break;
+ case TYPE_LTE:
+ run = mVarAOut <= mVarBOut;
+ break;
+ case TYPE_GT:
+ run = mVarAOut > mVarBOut;
+ break;
+ case TYPE_GTE:
+ run = mVarAOut >= mVarBOut;
+ break;
+ }
+ if (run) {
+ for (Operation op : mList) {
+ remoteContext.incrementOpCount();
+ op.apply(context.getContext());
+ }
+ }
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ /**
+ * Write the operation on the buffer
+ *
+ * @param type type of operation
+ * @param a first value
+ * @param b second value
+ * @param buffer the buffer to write to
+ */
+ public static void apply(@NonNull WireBuffer buffer, byte type, float a, float b) {
+ buffer.start(OP_CODE);
+ buffer.writeByte(type);
+ buffer.writeFloat(a);
+ buffer.writeFloat(b);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ byte type = (byte) buffer.readByte();
+ float a = buffer.readFloat();
+ float b = buffer.readFloat();
+ operations.add(new ConditionalOperations(type, a, b));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Operations", OP_CODE, name())
+ .description("Run if the condition is true")
+ .field(DocumentedOperation.BYTE, "type", "type of comparison")
+ .field(DocumentedOperation.FLOAT, "a", "first value")
+ .field(DocumentedOperation.FLOAT, "b", "second value");
+ }
+
+ /**
+ * Calculate and estimate of the number of iterations
+ *
+ * @return number of loops or 10 if based on variables
+ */
+ public int estimateIterations() {
+ return 1; // this is a generic estmate if the values are variables;
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("type", mType)
+ .add("varA", mVarA, mVarAOut)
+ .add("VarB", mVarB, mVarBOut)
+ .add("list", mList);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
index 4d2a939eb948..326f0600854e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
@@ -46,7 +46,7 @@ public class DrawContent extends PaintOperation implements Serializable {
*
* @param component
*/
- public void setComponent(LayoutComponent component) {
+ public void setComponent(@Nullable LayoutComponent component) {
mComponent = component;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index 31d9b6ac124d..5853578ab31e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -110,7 +110,7 @@ public class DrawRoundRect extends DrawBase6 {
"The x-radius of the oval used to round the corners")
.field(
DocumentedOperation.FLOAT,
- "sweepAngle",
+ "ry",
"The y-radius of the oval used to round the corners");
}
@@ -126,7 +126,6 @@ public class DrawRoundRect extends DrawBase6 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom", "rx", "sweepAngle")
- .addType(CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom", "rx", "ry").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index ee1689c21fe3..cec01c4e5b65 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -211,6 +211,7 @@ public class DrawText extends PaintOperation implements VariableSupport {
public void serialize(MapSerializer serializer) {
serializer
.addType(CLASS_NAME)
+ .add("textId", mTextID)
.add("start", mStart)
.add("end", mEnd)
.add("contextStart", mContextStart)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
index 233e246d3868..5096aaf03c9d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -42,6 +42,15 @@ public class FloatConstant extends Operation implements Serializable {
this.mValue = value;
}
+ /**
+ * Copy the value from another operation
+ *
+ * @param from value to copy from
+ */
+ public void update(FloatConstant from) {
+ mValue = from.mValue;
+ }
+
@Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mId, mValue);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/HapticFeedback.java b/core/java/com/android/internal/widget/remotecompose/core/operations/HapticFeedback.java
new file mode 100644
index 000000000000..1b7f5e84b171
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/HapticFeedback.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.widget.remotecompose.core.operations;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.SerializableToString;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
+import java.util.List;
+
+/** Generate HapticFeedback */
+public class HapticFeedback extends Operation implements SerializableToString, Serializable {
+ private static final int OP_CODE = Operations.HAPTIC_FEEDBACK;
+ private static final String CLASS_NAME = "HapticFeedback";
+ private int mHapticFeedbackType;
+
+ public HapticFeedback(int hapticFeedbackType) {
+ this.mHapticFeedbackType = hapticFeedbackType;
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mHapticFeedbackType);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return CLASS_NAME + "(" + mHapticFeedbackType + ")";
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * add a text data operation
+ *
+ * @param buffer buffer to add to
+ * @param hapticFeedbackType the vibration effect
+ */
+ public static void apply(@NonNull WireBuffer buffer, int hapticFeedbackType) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(hapticFeedbackType);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int hapticFeedbackType = buffer.readInt();
+
+ operations.add(new HapticFeedback(hapticFeedbackType));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+ .description("Generate an haptic feedback")
+ .field(DocumentedOperation.INT, "HapticFeedbackType", "Type of haptic feedback");
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {
+ context.hapticEffect(mHapticFeedbackType);
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return indent + toString();
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, getSerializedName() + "<" + mHapticFeedbackType + ">");
+ }
+
+ @NonNull
+ private String getSerializedName() {
+ return "HAPTIC_FEEDBACK";
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.addType(CLASS_NAME).add("hapticFeedbackType", mHapticFeedbackType);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCombine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCombine.java
new file mode 100644
index 000000000000..5f761d184e29
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCombine.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
+import java.util.List;
+
+/** Operation to perform Constructive area geometry operations, combining two Paths */
+public class PathCombine extends PaintOperation implements VariableSupport, Serializable {
+ private static final int OP_CODE = Operations.PATH_COMBINE;
+ private static final String CLASS_NAME = "PathCombine";
+ public int mOutId;
+ public int mPathId1;
+ public int mPathId2;
+ private byte mOperation;
+
+ /** Subtract the second path from the first path. */
+ public static final byte OP_DIFFERENCE = 0;
+
+ /** Intersect the second path with the first path. */
+ public static final byte OP_INTERSECT = 1;
+
+ /** Subtract the first path from the second path. */
+ public static final byte OP_REVERSE_DIFFERENCE = 2;
+
+ /** Union (inclusive-or) the two paths. */
+ public static final byte OP_UNION = 3;
+
+ /** Exclusive-or the two paths. */
+ public static final byte OP_XOR = 4;
+
+ public PathCombine(int outId, int pathId1, int pathId2, byte operation) {
+ this.mOutId = outId;
+ this.mPathId1 = pathId1;
+ this.mPathId2 = pathId2;
+ this.mOperation = operation;
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {}
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {}
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mOutId, mPathId1, mPathId2, mOperation);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return CLASS_NAME
+ + "["
+ + mOutId
+ + "] = ["
+ + mPathId1
+ + " ] + [ "
+ + mPathId2
+ + "], "
+ + mOperation;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * Writes out the operation to the buffer
+ *
+ * @param buffer buffer to write to
+ * @param outId id of the path
+ * @param pathId1 source path 1
+ * @param pathId2 source path 2
+ * @param op the operation to perform
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer, int outId, int pathId1, int pathId2, byte op) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(outId);
+ buffer.writeInt(pathId1);
+ buffer.writeInt(pathId2);
+ buffer.writeByte(op);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int outId1 = buffer.readInt();
+ int pathId1 = buffer.readInt();
+ int pathId2 = buffer.readInt();
+ byte op = (byte) buffer.readByte();
+ operations.add(new PathCombine(outId1, pathId1, pathId2, op));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+ .description("Merge two string into one")
+ .field(INT, "srcPathId1", "id of the path")
+ .field(INT, "srcPathId1", "x Shift of the path")
+ .field(DocumentedOperation.BYTE, "operation", "the operation");
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.combinePath(mOutId, mPathId1, mPathId2, mOperation);
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("outId", mOutId)
+ .add("pathId1", mPathId1)
+ .add("pathId2", mPathId2)
+ .add("operation", mOperation);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 67773d1d6187..d8ef4cbba4d6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -37,7 +37,7 @@ public class TextData extends Operation implements SerializableToString, Seriali
private static final int OP_CODE = Operations.DATA_TEXT;
private static final String CLASS_NAME = "TextData";
public final int mTextId;
- @NonNull public final String mText;
+ @NonNull public String mText;
public static final int MAX_STRING_SIZE = 4000;
public TextData(int textId, @NonNull String text) {
@@ -45,6 +45,15 @@ public class TextData extends Operation implements SerializableToString, Seriali
this.mText = text;
}
+ /**
+ * Copy the data from another text data
+ *
+ * @param from source to copy from
+ */
+ public void update(TextData from) {
+ mText = from.mText;
+ }
+
@Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mTextId, mText);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index 1239b5648446..2bc77cc48839 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
@@ -31,7 +32,7 @@ import com.android.internal.widget.remotecompose.core.serialize.Serializable;
import java.util.List;
/** Operation to deal with Text data */
-public class TextMerge extends Operation implements Serializable {
+public class TextMerge extends Operation implements VariableSupport, Serializable {
private static final int OP_CODE = Operations.TEXT_MERGE;
private static final String CLASS_NAME = "TextMerge";
public int mTextId;
@@ -123,10 +124,21 @@ public class TextMerge extends Operation implements Serializable {
context.loadText(mTextId, str1 + str2);
}
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ apply(context);
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ context.listensTo(mSrcId1, this);
+ context.listensTo(mSrcId2, this);
+ }
+
@NonNull
@Override
public String deepToString(@NonNull String indent) {
- return indent + toString();
+ return indent + this;
}
@Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
index fd9a2bf5e435..e9cc26f58c9b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
@@ -275,7 +275,7 @@ public class TimeAttribute extends PaintOperation {
ctx.loadFloat(mId, time.getSecond());
break;
case TIME_IN_MIN:
- ctx.loadFloat(mId, time.getDayOfMonth());
+ ctx.loadFloat(mId, time.getMinute());
break;
case TIME_IN_HR:
ctx.loadFloat(mId, time.getHour());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
index 25a10ab7dbeb..e473d9e392d2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
@@ -27,7 +27,6 @@ import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
-import com.android.internal.widget.remotecompose.core.operations.DrawContent;
import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import com.android.internal.widget.remotecompose.core.serialize.Serializable;
@@ -164,12 +163,10 @@ public class CanvasOperations extends PaintOperation
*
* @param layoutComponent
*/
- public void setComponent(LayoutComponent layoutComponent) {
+ public void setComponent(@Nullable LayoutComponent layoutComponent) {
mComponent = layoutComponent;
- for (Operation op : mList) {
- if (op instanceof DrawContent) {
- ((DrawContent) op).setComponent(layoutComponent);
- }
+ if (layoutComponent != null) {
+ layoutComponent.setCanvasOperations(this);
}
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index b30dade828a7..f1158d91f94b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.SerializableToString;
import com.android.internal.widget.remotecompose.core.TouchListener;
import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
@@ -556,6 +557,7 @@ public class Component extends PaintOperation
ComponentMeasure m = measure.get(this);
if (!mFirstLayout
&& context.isAnimationEnabled()
+ && mAnimationSpec.isAnimationEnabled()
&& !(this instanceof LayoutComponentContent)) {
if (mAnimateMeasure == null) {
ComponentMeasure origin =
@@ -1129,26 +1131,15 @@ public class Component extends PaintOperation
}
}
- /** Extract CanvasOperations if present */
- public @Nullable CanvasOperations getCanvasOperations(LayoutComponent layoutComponent) {
- for (Operation op : mList) {
- if (op instanceof CanvasOperations) {
- ((CanvasOperations) op).setComponent(layoutComponent);
- return (CanvasOperations) op;
- }
- }
- return null;
- }
-
/**
- * Extract child TextData elements
+ * Extract child Data elements
*
- * @param data an ArrayList that will be populated with the TextData elements (if any)
+ * @param data an ArrayList that will be populated with the Data elements (if any)
*/
- public void getData(@NonNull ArrayList<TextData> data) {
+ public void getData(@NonNull ArrayList<Operation> data) {
for (Operation op : mList) {
- if (op instanceof TextData) {
- data.add((TextData) op);
+ if (op instanceof TextData || op instanceof BitmapData) {
+ data.add(op);
}
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
index 0e629c5d2448..3e1cf35e153a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
@@ -117,7 +117,7 @@ public class ImpulseOperation extends PaintOperation implements VariableSupport,
@NonNull
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("LoopOperation\n");
+ StringBuilder builder = new StringBuilder("ImpulseOperation\n");
for (Operation operation : mList) {
builder.append(" startAt: ");
builder.append(mStartAt);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index 7e2a4ccec222..6163d8099b8c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -30,7 +30,6 @@ import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
import com.android.internal.widget.remotecompose.core.operations.PaintData;
-import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
@@ -127,9 +126,18 @@ public class LayoutComponent extends Component {
// Should be removed after ImageLayout is in
private static final boolean USE_IMAGE_TEMP_FIX = true;
+ /**
+ * Set canvas operations op on this component
+ *
+ * @param operations
+ */
+ public void setCanvasOperations(@Nullable CanvasOperations operations) {
+ mDrawContentOperations = operations;
+ }
+
@Override
public void inflate() {
- ArrayList<TextData> data = new ArrayList<>();
+ ArrayList<Operation> data = new ArrayList<>();
ArrayList<Operation> supportedOperations = new ArrayList<>();
for (Operation op : mList) {
@@ -139,7 +147,6 @@ public class LayoutComponent extends Component {
mChildrenComponents.clear();
LayoutComponentContent content = (LayoutComponentContent) op;
content.getComponents(mChildrenComponents);
- mDrawContentOperations = content.getCanvasOperations(this);
if (USE_IMAGE_TEMP_FIX) {
if (mChildrenComponents.isEmpty() && !mContent.mList.isEmpty()) {
CanvasContent canvasContent =
@@ -178,8 +185,6 @@ public class LayoutComponent extends Component {
((ScrollModifierOperation) op).inflate(this);
}
mComponentModifiers.add((ModifierOperation) op);
- } else if (op instanceof TextData) {
- data.add((TextData) op);
} else if (op instanceof TouchExpression
|| (op instanceof PaintData)
|| (op instanceof FloatExpression)) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index dda328f57a3b..e5914eb7fd28 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -211,7 +211,6 @@ public class LoopOperation extends PaintOperation
.add("until", mUntil, mUntilOut)
.add("from", mFrom, mFromOut)
.add("step", mStep, mStepOut)
- .add("untilOut", mUntilOut)
.add("list", mList);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index c87bbdc9809d..4f552e7a9aaa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -35,6 +35,7 @@ import java.util.List;
/** Basic component animation spec */
public class AnimationSpec extends Operation implements ModifierOperation {
public static final AnimationSpec DEFAULT = new AnimationSpec();
+ public static final AnimationSpec DISABLED = new AnimationSpec(0);
int mAnimationId = -1;
float mMotionDuration = 300;
int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
@@ -71,6 +72,15 @@ public class AnimationSpec extends Operation implements ModifierOperation {
ANIMATION.FADE_OUT);
}
+ public AnimationSpec(int value) {
+ this();
+ mAnimationId = value;
+ }
+
+ public boolean isAnimationEnabled() {
+ return mAnimationId != 0;
+ }
+
public int getAnimationId() {
return mAnimationId;
}
@@ -130,6 +140,7 @@ public class AnimationSpec extends Operation implements ModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addType("AnimationSpec")
+ .add("animationId", mAnimationId)
.add("motionDuration", getMotionDuration())
.add("motionEasingType", Easing.getString(getMotionEasingType()))
.add("visibilityDuration", getVisibilityDuration())
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index f9111dffe2c4..8e1c872aec1f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -155,23 +155,5 @@ public class CanvasLayout extends BoxLayout {
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
serializer.addType(getSerializedName());
- serializer.add("horizontalPositioning", mHorizontalPositioning);
- }
-
- private String getPositioningString(int pos) {
- switch (pos) {
- case START:
- return "START";
- case CENTER:
- return "CENTER";
- case END:
- return "END";
- case TOP:
- return "TOP";
- case BOTTOM:
- return "BOTTOM";
- default:
- return "NONE";
- }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java
new file mode 100644
index 000000000000..a4ed0c1319d4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ImageLayout.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+public class ImageLayout extends LayoutManager implements VariableSupport {
+
+ private static final boolean DEBUG = false;
+ private int mBitmapId = -1;
+ private int mScaleType;
+ private float mAlpha = 1f;
+
+ @NonNull ImageScaling mScaling = new ImageScaling();
+ @NonNull PaintBundle mPaint = new PaintBundle();
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ if (mBitmapId != -1) {
+ context.listensTo(mBitmapId, this);
+ }
+ }
+
+ public ImageLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int bitmapId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int scaleType,
+ float alpha) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mBitmapId = bitmapId;
+ mScaleType = scaleType & 0xFF;
+ mAlpha = alpha;
+ }
+
+ public ImageLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int bitmapId,
+ int scaleType,
+ float alpha) {
+ this(parent, componentId, animationId, bitmapId, 0, 0, 0, 0, scaleType, alpha);
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+
+ BitmapData bitmapData = (BitmapData) context.getContext().getObject(mBitmapId);
+ if (bitmapData != null) {
+ size.setWidth(bitmapData.getWidth());
+ size.setHeight(bitmapData.getHeight());
+ }
+ }
+
+ @Override
+ public void computeSize(
+ @NonNull PaintContext context,
+ float minWidth,
+ float maxWidth,
+ float minHeight,
+ float maxHeight,
+ @NonNull MeasurePass measure) {
+ float modifiersWidth = computeModifierDefinedWidth(context.getContext());
+ float modifiersHeight = computeModifierDefinedHeight(context.getContext());
+ ComponentMeasure m = measure.get(this);
+ m.setW(modifiersWidth);
+ m.setH(modifiersHeight);
+ }
+
+ @Override
+ public void paintingComponent(@NonNull PaintContext context) {
+ context.save();
+ context.translate(mX, mY);
+ mComponentModifiers.paint(context);
+ float tx = mPaddingLeft;
+ float ty = mPaddingTop;
+ context.translate(tx, ty);
+ float w = mWidth - mPaddingLeft - mPaddingRight;
+ float h = mHeight - mPaddingTop - mPaddingBottom;
+ context.clipRect(0f, 0f, w, h);
+
+ BitmapData bitmapData = (BitmapData) context.getContext().getObject(mBitmapId);
+ if (bitmapData != null) {
+ mScaling.setup(
+ 0f,
+ 0f,
+ bitmapData.getWidth(),
+ bitmapData.getHeight(),
+ 0f,
+ 0f,
+ w,
+ h,
+ mScaleType,
+ 1f);
+
+ context.savePaint();
+ if (mAlpha == 1f) {
+ context.drawBitmap(
+ mBitmapId,
+ (int) 0f,
+ (int) 0f,
+ (int) bitmapData.getWidth(),
+ (int) bitmapData.getHeight(),
+ (int) mScaling.mFinalDstLeft,
+ (int) mScaling.mFinalDstTop,
+ (int) mScaling.mFinalDstRight,
+ (int) mScaling.mFinalDstBottom,
+ -1);
+ } else {
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(0f, 0f, 0f, mAlpha);
+ context.applyPaint(mPaint);
+ context.drawBitmap(
+ mBitmapId,
+ (int) 0f,
+ (int) 0f,
+ (int) bitmapData.getWidth(),
+ (int) bitmapData.getHeight(),
+ (int) mScaling.mFinalDstLeft,
+ (int) mScaling.mFinalDstTop,
+ (int) mScaling.mFinalDstRight,
+ (int) mScaling.mFinalDstBottom,
+ -1);
+ context.restorePaint();
+ }
+ context.restorePaint();
+ }
+
+ // debugBox(this, context);
+ context.translate(-tx, -ty);
+ context.restore();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "IMAGE_LAYOUT ["
+ + mComponentId
+ + ":"
+ + mAnimationId
+ + "] ("
+ + mX
+ + ", "
+ + mY
+ + " - "
+ + mWidth
+ + " x "
+ + mHeight
+ + ") "
+ + mVisibility;
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "IMAGE_LAYOUT";
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(
+ indent,
+ getSerializedName()
+ + " ["
+ + mComponentId
+ + ":"
+ + mAnimationId
+ + "] = "
+ + "["
+ + mX
+ + ", "
+ + mY
+ + ", "
+ + mWidth
+ + ", "
+ + mHeight
+ + "] "
+ + mVisibility
+ + " ("
+ + mBitmapId
+ + "\")");
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return "ImageLayout";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_IMAGE;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer
+ * @param componentId
+ * @param animationId
+ * @param bitmapId
+ * @param scaleType
+ * @param alpha
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int bitmapId,
+ int scaleType,
+ float alpha) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(bitmapId);
+ buffer.writeInt(scaleType);
+ buffer.writeFloat(alpha);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int bitmapId = buffer.readInt();
+ int scaleType = buffer.readInt();
+ float alpha = buffer.readFloat();
+ operations.add(new ImageLayout(null, componentId, animationId, bitmapId, scaleType, alpha));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Image layout implementation.\n\n")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(
+ INT,
+ "ANIMATION_ID",
+ "id used to match components," + " for animation purposes")
+ .field(INT, "BITMAP_ID", "bitmap id")
+ .field(INT, "SCALE_TYPE", "scale type")
+ .field(FLOAT, "ALPHA", "alpha");
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mComponentId, mAnimationId, mBitmapId, mScaleType, mAlpha);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index 2595a71abaa5..d383ee9e4fc9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -31,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.operations.Utils;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
@@ -73,6 +74,8 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
private float mTextW = -1;
private float mTextH = -1;
+ private final Size mCachedSize = new Size(0f, 0f);
+
@Nullable private String mCachedString = "";
Platform.ComputedTextLayout mComputedTextLayout;
@@ -230,6 +233,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
case TEXT_ALIGN_START:
default:
}
+
if (mTextW > (mWidth - mPaddingLeft - mPaddingRight)) {
context.save();
context.clipRect(
@@ -317,6 +321,21 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
}
@Override
+ public void computeSize(
+ @NonNull PaintContext context,
+ float minWidth,
+ float maxWidth,
+ float minHeight,
+ float maxHeight,
+ @NonNull MeasurePass measure) {
+ super.computeSize(context, minWidth, maxWidth, minHeight, maxHeight, measure);
+ computeWrapSize(context, maxWidth, maxHeight, true, true, measure, mCachedSize);
+ ComponentMeasure m = measure.get(this);
+ m.setW(mCachedSize.getWidth());
+ m.setH(mCachedSize.getHeight());
+ }
+
+ @Override
public void computeWrapSize(
@NonNull PaintContext context,
float maxWidth,
@@ -335,6 +354,8 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
if (mCachedString == null) {
return;
}
+
+ boolean forceComplex = false;
int flags = PaintContext.TEXT_MEASURE_FONT_HEIGHT | PaintContext.TEXT_MEASURE_SPACES;
if (mMaxLines == 1
&& (mOverflow == OVERFLOW_START_ELLIPSIS
@@ -342,8 +363,20 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
|| mOverflow == OVERFLOW_ELLIPSIS)) {
flags |= PaintContext.TEXT_COMPLEX;
}
- context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
- if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1 && maxWidth > 0f) {
+ if ((flags & PaintContext.TEXT_COMPLEX) != PaintContext.TEXT_COMPLEX) {
+ for (int i = 0; i < mCachedString.length(); i++) {
+ char c = mCachedString.charAt(i);
+ if ((c == '\n') || (c == '\t')) {
+ flags |= PaintContext.TEXT_COMPLEX;
+ forceComplex = true;
+ break;
+ }
+ }
+ }
+ if (!forceComplex) {
+ context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
+ }
+ if (forceComplex || bounds[2] - bounds[1] > maxWidth && mMaxLines > 1 && maxWidth > 0f) {
mComputedTextLayout =
context.layoutComplexText(
mTextId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index 656a3c0fca68..b3d76b765143 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -250,7 +250,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
context.savePaint();
paint.reset();
paint.setColor(mR, mG, mB, mA);
- paint.setStrokeWidth(mBorderWidth);
+ paint.setStrokeWidth(mBorderWidth * context.getContext().getDensity());
paint.setStyle(PaintBundle.STYLE_STROKE);
context.replacePaint(paint);
if (mShapeType == ShapeType.RECTANGLE) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java
new file mode 100644
index 000000000000..2170efd68a64
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionMetadataOperation.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.SerializableToString;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+import com.android.internal.widget.remotecompose.core.serialize.SerializeTags;
+
+import java.util.List;
+
+/** Capture a host action information. This can be triggered on eg. a click. */
+public class HostActionMetadataOperation extends Operation
+ implements ActionOperation, SerializableToString, Serializable {
+ private static final int OP_CODE = Operations.HOST_METADATA_ACTION;
+
+ int mActionId = -1;
+ int mMetadataId = -1;
+
+ public HostActionMetadataOperation(int id, int metadataId) {
+ mActionId = id;
+ mMetadataId = metadataId;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "HostMetadataActionOperation(" + mActionId + ":" + mMetadataId + ")";
+ }
+
+ public int getActionId() {
+ return mActionId;
+ }
+
+ /**
+ * Returns the serialized name for this operation
+ *
+ * @return the serialized name
+ */
+ @NonNull
+ public String serializedName() {
+ return "HOST_METADATA_ACTION";
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, serializedName() + " = " + mActionId + ", " + mMetadataId);
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {}
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {}
+
+ @Override
+ public void runAction(
+ @NonNull RemoteContext context,
+ @NonNull CoreDocument document,
+ @NonNull Component component,
+ float x,
+ float y) {
+ String metadata = context.getText(mMetadataId);
+ if (metadata == null) {
+ metadata = "";
+ }
+ context.runAction(mActionId, metadata);
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param actionId the action id
+ */
+ public static void apply(@NonNull WireBuffer buffer, int actionId, int metadataId) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(actionId);
+ buffer.writeInt(metadataId);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int actionId = buffer.readInt();
+ int metadataId = buffer.readInt();
+ operations.add(new HostActionMetadataOperation(actionId, metadataId));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", OP_CODE, "HostAction")
+ .description("Host action. This operation represents a host action")
+ .field(INT, "ACTION_ID", "Host Action ID")
+ .field(INT, "METADATA", "Host Action Text Metadata ID");
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addTags(SerializeTags.MODIFIER)
+ .addType("HostActionOperation")
+ .add("id", mActionId)
+ .add("metadata", mMetadataId);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
index 466e435e20cf..3e1f32de66e4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
@@ -401,10 +401,8 @@ public class ScrollModifierOperation extends ListActionsOperation
.add("direction", mDirection)
.add("max", mMax)
.add("notchMax", mNotchMax)
- .add("scrollX", mScrollX)
- .add("scrollY", mScrollY)
- .add("maxScrollX", mMaxScrollX)
- .add("maxScrollY", mMaxScrollY)
+ .add("scrollValue", isVerticalScroll() ? mScrollY : mScrollX)
+ .add("maxScrollValue", isVerticalScroll() ? mMaxScrollY : mMaxScrollX)
.add("contentDimension", mContentDimension)
.add("hostDimension", mHostDimension);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
index bdc765968387..25dcb67fe9e2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -34,14 +34,23 @@ import java.util.List;
public class IntegerConstant extends Operation implements Serializable {
private static final String CLASS_NAME = "IntegerConstant";
- private final int mValue;
- private final int mId;
+ private int mValue;
+ public final int mId;
IntegerConstant(int id, int value) {
mId = id;
mValue = value;
}
+ /**
+ * Updates the value of the integer constant
+ *
+ * @param ic the integer constant to copy
+ */
+ public void update(IntegerConstant ic) {
+ mValue = ic.mValue;
+ }
+
@Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mId, mValue);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index d071e0a21d22..ab0f7352182a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -36,7 +36,7 @@ public class LongConstant extends Operation implements Serializable {
private static final int OP_CODE = Operations.DATA_LONG;
private long mValue;
- private final int mId;
+ public final int mId;
/**
* @param id the id of the constant
@@ -48,6 +48,15 @@ public class LongConstant extends Operation implements Serializable {
}
/**
+ * Copy the value from another longConstant
+ *
+ * @param from the constant to copy from
+ */
+ public void update(LongConstant from) {
+ mValue = from.mValue;
+ }
+
+ /**
* Get the value of the long constant
*
* @return the value of the long
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 1f9a27429067..f5b2cca15e43 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -41,6 +41,7 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.RemoteContextAware;
import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.player.platform.AndroidRemoteContext;
import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
/** A view to to display and play RemoteCompose documents */
@@ -114,6 +115,23 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa
return mInner.getDocument();
}
+ /**
+ * This will update values in the already loaded document.
+ *
+ * @param value the document to update variables in the current document width
+ */
+ public void updateDocument(RemoteComposeDocument value) {
+ RemoteComposeDocument document = value;
+ AndroidRemoteContext tmpContext = new AndroidRemoteContext();
+ document.initializeContext(tmpContext);
+ float density = getContext().getResources().getDisplayMetrics().density;
+ tmpContext.setAnimationEnabled(true);
+ tmpContext.setDensity(density);
+ tmpContext.setUseChoreographer(false);
+ mInner.getDocument().mDocument.applyUpdate(document.mDocument);
+ mInner.invalidate();
+ }
+
public void setDocument(RemoteComposeDocument value) {
if (value != null) {
if (value.canBeDisplayed(
@@ -312,7 +330,8 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa
}
/**
- * Add a callback for handling id actions events on the document
+ * Add a callback for handling id actions events on the document. Can only be added after the
+ * document has been loaded.
*
* @param callback the callback lambda that will be used when a action is executed
* <p>The parameter of the callback are:
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index b5aedd8d0231..680a221cc2db 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -808,6 +808,24 @@ public class AndroidPaintContext extends PaintContext {
}
@Override
+ public void combinePath(int out, int path1, int path2, byte operation) {
+ Path p1 = getPath(path1, 0, 1);
+ Path p2 = getPath(path2, 0, 1);
+ Path.Op[] op = {
+ Path.Op.DIFFERENCE,
+ Path.Op.INTERSECT,
+ Path.Op.REVERSE_DIFFERENCE,
+ Path.Op.UNION,
+ Path.Op.XOR,
+ };
+ Path p = new Path(p1);
+ p.op(p2, op[operation]);
+
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ androidContext.mRemoteComposeState.putPath(out, p);
+ }
+
+ @Override
public void reset() {
mPaint.reset();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index b31c76056e38..e1f2924021a4 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -197,7 +197,7 @@ public class AndroidRemoteContext extends RemoteContext {
@Override
public void runAction(int id, @NonNull String metadata) {
- mDocument.performClick(this, id);
+ mDocument.performClick(this, id, metadata);
}
@Override
@@ -392,6 +392,11 @@ public class AndroidRemoteContext extends RemoteContext {
}
@Override
+ public long getLong(int id) {
+ return ((LongConstant) mRemoteComposeState.getObject(id)).getValue();
+ }
+
+ @Override
public int getColor(int id) {
return mRemoteComposeState.getColor(id);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 29cd40def562..0bc99abc17bc 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -154,7 +154,11 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
param.leftMargin = (int) area.getLeft();
param.topMargin = (int) area.getTop();
viewArea.setOnClickListener(
- view1 -> mDocument.getDocument().performClick(mARContext, area.getId()));
+ view1 ->
+ mDocument
+ .getDocument()
+ .performClick(
+ mARContext, area.getId(), area.getMetadata()));
addView(viewArea, param);
}
if (!clickAreas.isEmpty()) {
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index ba7e70564143..95bab542f9d6 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -204,7 +204,7 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
return reinterpret_cast<jlong>(connection);
}
-static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
+static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr, jboolean fast) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
if (connection) {
@@ -212,6 +212,13 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
if (connection->tableQuery != nullptr) {
sqlite3_finalize(connection->tableQuery);
}
+ if (fast) {
+ // The caller requested a fast close, so do not checkpoint even if this is the last
+ // connection to the database. Note that the change is only to this connection.
+ // Any other connections to the same database are unaffected.
+ int _unused = 0;
+ sqlite3_db_config(connection->db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &_unused);
+ }
int err = sqlite3_close(connection->db);
if (err != SQLITE_OK) {
// This can happen if sub-objects aren't closed first. Make sure the caller knows.
@@ -959,8 +966,8 @@ static const JNINativeMethod sMethods[] =
/* name, signature, funcPtr */
{ "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZII)J",
(void*)nativeOpen },
- { "nativeClose", "(J)V",
- (void*)nativeClose },
+ { "nativeClose", "(JZ)V",
+ (void*) static_cast<void(*)(JNIEnv*,jclass,jlong,jboolean)>(nativeClose) },
{ "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V",
(void*)nativeRegisterCustomScalarFunction },
{ "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V",
diff --git a/core/jni/android_media_ImageWriter.cpp b/core/jni/android_media_ImageWriter.cpp
index 1357dd842ff1..8e58922bd9df 100644
--- a/core/jni/android_media_ImageWriter.cpp
+++ b/core/jni/android_media_ImageWriter.cpp
@@ -399,7 +399,7 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje
}
sp<JNIImageWriterContext> ctx(new JNIImageWriterContext(env, weakThiz, clazz));
- sp<Surface> producer = new Surface(bufferProducer, /*controlledByApp*/false);
+ sp<Surface> producer = sp<Surface>::make(bufferProducer, /*controlledByApp*/ false);
ctx->setProducer(producer);
/**
* NATIVE_WINDOW_API_CPU isn't a good choice here, as it makes the bufferQueue not connectable
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index dec724b6a7ff..b4c58b9b246a 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -558,8 +558,7 @@ static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr
delete parcel;
}
-static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
-{
+static Parcel* parcel_for_marshall(JNIEnv* env, jlong nativePtr) {
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel == NULL) {
return NULL;
@@ -577,6 +576,16 @@ static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong na
return NULL;
}
+ return parcel;
+}
+
+static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
+{
+ Parcel* parcel = parcel_for_marshall(env, nativePtr);
+ if (parcel == NULL) {
+ return NULL;
+ }
+
jbyteArray ret = env->NewByteArray(parcel->dataSize());
if (ret != NULL)
@@ -592,6 +601,58 @@ static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong na
return ret;
}
+static long ensure_capacity(JNIEnv* env, Parcel* parcel, jint remaining) {
+ long dataSize = parcel->dataSize();
+ if (remaining < dataSize) {
+ jniThrowExceptionFmt(env, "java/nio/BufferOverflowException",
+ "Destination buffer remaining capacity %d is less than the Parcel data size %d.",
+ remaining, dataSize);
+ return -1;
+ }
+ return dataSize;
+}
+
+static int android_os_Parcel_marshall_array(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jbyteArray data, jint offset, jint remaining)
+{
+ Parcel* parcel = parcel_for_marshall(env, nativePtr);
+ if (parcel == NULL) {
+ return 0;
+ }
+
+ long data_size = ensure_capacity(env, parcel, remaining);
+ if (data_size < 0) {
+ return 0;
+ }
+
+ jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
+ if (array != NULL)
+ {
+ memcpy(array + offset, parcel->data(), data_size);
+ env->ReleasePrimitiveArrayCritical(data, array, 0);
+ }
+ return data_size;
+}
+
+static int android_os_Parcel_marshall_buffer(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jobject javaBuffer, jint offset, jint remaining) {
+ Parcel* parcel = parcel_for_marshall(env, nativePtr);
+ if (parcel == NULL) {
+ return 0;
+ }
+
+ long data_size = ensure_capacity(env, parcel, remaining);
+ if (data_size < 0) {
+ return 0;
+ }
+
+ jbyte* buffer = (jbyte*)env->GetDirectBufferAddress(javaBuffer);
+ if (buffer != NULL) {
+ memcpy(buffer + offset, parcel->data(), data_size);
+ }
+ return data_size;
+}
+
static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
jbyteArray data, jint offset, jint length)
{
@@ -613,6 +674,25 @@ static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong native
}
}
+static void android_os_Parcel_unmarshall_buffer(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jobject javaBuffer, jint offset, jint length)
+{
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel == NULL || length < 0) {
+ return;
+ }
+
+ jbyte* buffer = (jbyte*)env->GetDirectBufferAddress(javaBuffer);
+ if (buffer)
+ {
+ parcel->setDataSize(length);
+ parcel->setDataPosition(0);
+
+ void* raw = parcel->writeInplace(length);
+ memcpy(raw, (buffer + offset), length);
+ }
+}
+
static jint android_os_Parcel_compareData(JNIEnv* env, jclass clazz, jlong thisNativePtr,
jlong otherNativePtr)
{
@@ -911,7 +991,10 @@ static const JNINativeMethod gParcelMethods[] = {
{"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy},
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
+ {"nativeMarshallArray", "(J[BII)I", (void*)android_os_Parcel_marshall_array},
+ {"nativeMarshallBuffer", "(JLjava/nio/ByteBuffer;II)I", (void*)android_os_Parcel_marshall_buffer},
{"nativeUnmarshall", "(J[BII)V", (void*)android_os_Parcel_unmarshall},
+ {"nativeUnmarshallBuffer", "(JLjava/nio/ByteBuffer;II)V", (void*)android_os_Parcel_unmarshall_buffer},
{"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData},
{"nativeCompareDataInRange", "(JIJII)Z", (void*)android_os_Parcel_compareDataInRange},
{"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom},
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index e874163943b6..a6a748c3deb7 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -55,41 +55,25 @@ public:
inline std::shared_ptr<InputChannel> getInputChannel() { return mInputChannel; }
- void setDisposeCallback(InputChannelObjDisposeCallback callback, void* data);
void dispose(JNIEnv* env, jobject obj);
private:
std::shared_ptr<InputChannel> mInputChannel;
- InputChannelObjDisposeCallback mDisposeCallback;
- void* mDisposeData;
};
// ----------------------------------------------------------------------------
NativeInputChannel::NativeInputChannel(std::unique_ptr<InputChannel> inputChannel)
- : mInputChannel(std::move(inputChannel)), mDisposeCallback(nullptr) {}
+ : mInputChannel(std::move(inputChannel)) {}
NativeInputChannel::~NativeInputChannel() {
}
-void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
- if (input_flags::remove_input_channel_from_windowstate()) {
- return;
- }
- mDisposeCallback = callback;
- mDisposeData = data;
-}
-
void NativeInputChannel::dispose(JNIEnv* env, jobject obj) {
if (!mInputChannel) {
return;
}
- if (mDisposeCallback) {
- mDisposeCallback(env, obj, mInputChannel, mDisposeData);
- mDisposeCallback = nullptr;
- mDisposeData = nullptr;
- }
mInputChannel.reset();
}
@@ -108,17 +92,6 @@ std::shared_ptr<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv*
return nativeInputChannel != nullptr ? nativeInputChannel->getInputChannel() : nullptr;
}
-void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
- InputChannelObjDisposeCallback callback, void* data) {
- NativeInputChannel* nativeInputChannel =
- android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
- if (!nativeInputChannel || !nativeInputChannel->getInputChannel()) {
- ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
- } else {
- nativeInputChannel->setDisposeCallback(callback, data);
- }
-}
-
static jlong android_view_InputChannel_createInputChannel(
JNIEnv* env, std::unique_ptr<InputChannel> inputChannel) {
std::unique_ptr<NativeInputChannel> nativeInputChannel =
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 312c2067d396..783daec82b9e 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -139,7 +139,7 @@ jobject android_view_Surface_createFromIGraphicBufferProducer(JNIEnv* env,
return NULL;
}
- sp<Surface> surface(new Surface(bufferProducer, true));
+ sp<Surface> surface = sp<Surface>::make(bufferProducer, true);
return android_view_Surface_createFromSurface(env, surface);
}
@@ -161,7 +161,7 @@ static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
return 0;
}
- sp<Surface> surface(new Surface(producer, true));
+ sp<Surface> surface = sp<Surface>::make(producer, true);
if (surface == NULL) {
jniThrowException(env, OutOfResourcesException, NULL);
return 0;
@@ -358,8 +358,8 @@ static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
sp<Surface> sur;
if (surfaceShim.graphicBufferProducer != nullptr) {
// we have a new IGraphicBufferProducer, create a new Surface for it
- sur = new Surface(surfaceShim.graphicBufferProducer, true,
- surfaceShim.surfaceControlHandle);
+ sur = sp<Surface>::make(surfaceShim.graphicBufferProducer, true,
+ surfaceShim.surfaceControlHandle);
// and keep a reference before passing to java
sur->incStrong(&sRefBaseOwner);
}
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 6ad109e80752..4f2ab09252c8 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -42,14 +42,14 @@ sp<SurfaceComposerClient> android_view_SurfaceSession_getClient(
static jlong nativeCreate(JNIEnv* env, jclass clazz) {
- SurfaceComposerClient* client = new SurfaceComposerClient();
- client->incStrong((void*)nativeCreate);
- return reinterpret_cast<jlong>(client);
+ // Will be deleted via decStrong() in nativeDestroy.
+ auto client = sp<SurfaceComposerClient>::make();
+ return reinterpret_cast<jlong>(client.release());
}
static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
- client->decStrong((void*)nativeCreate);
+ client->decStrong((void*)client);
}
static const JNINativeMethod gMethods[] = {
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 21fe1f020b29..f71878ccff08 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -85,7 +85,7 @@ static void android_view_TextureView_createNativeWindow(JNIEnv* env, jobject tex
jobject surface) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surface));
- sp<ANativeWindow> window = new Surface(producer, true);
+ sp<ANativeWindow> window = sp<Surface>::make(producer, true);
window->incStrong((void*)android_view_TextureView_createNativeWindow);
SET_LONG(textureView, gTextureViewClassInfo.nativeWindow, jlong(window.get()));
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 48c92c87f54e..886b9f1ae658 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -201,8 +201,8 @@ bool punchHoles(const char *filePath, const uint64_t offset,
return true;
}
-bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
- std::vector<Elf64_Phdr> &programHeaders) {
+read_elf_status_t getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
+ std::vector<Elf64_Phdr> &programHeaders) {
// Open Elf file
Elf64_Ehdr ehdr;
std::ifstream inputStream(filePath, std::ifstream::in);
@@ -212,13 +212,13 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
// read executable headers
inputStream.read((char *)&ehdr, sizeof(ehdr));
if (!inputStream.good()) {
- return false;
+ return ELF_READ_ERROR;
}
- // only consider elf64 for punching holes
+ // only consider ELF64 files
if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
ALOGW("Provided file is not ELF64");
- return false;
+ return ELF_IS_NOT_64_BIT;
}
// read the program headers from elf file
@@ -229,7 +229,7 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
uint64_t phOffset;
if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) {
ALOGE("Overflow occurred when calculating phOffset");
- return false;
+ return ELF_READ_ERROR;
}
inputStream.seekg(phOffset);
@@ -237,7 +237,7 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
Elf64_Phdr header;
inputStream.read((char *)&header, sizeof(header));
if (!inputStream.good()) {
- return false;
+ return ELF_READ_ERROR;
}
if (header.p_type != PT_LOAD) {
@@ -246,13 +246,14 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
programHeaders.push_back(header);
}
- return true;
+ return ELF_READ_OK;
}
bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
std::vector<Elf64_Phdr> programHeaders;
- if (!getLoadSegmentPhdrs(filePath, offset, programHeaders)) {
- ALOGE("Failed to read program headers from ELF file.");
+ read_elf_status_t status = getLoadSegmentPhdrs(filePath, offset, programHeaders);
+ if (status != ELF_READ_OK) {
+ ALOGE("Failed to read program headers from 64 bit ELF file.");
return false;
}
return punchHoles(filePath, offset, programHeaders);
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
index 4a95686c5a0c..c4dc1115e6c4 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -22,6 +22,12 @@
namespace android {
+enum read_elf_status_t {
+ ELF_IS_NOT_64_BIT = -2,
+ ELF_READ_ERROR = -1,
+ ELF_READ_OK = 0,
+};
+
/*
* This function deallocates space used by zero padding at the end of LOAD segments in given
* uncompressed ELF file. Read ELF headers and find out the offset and sizes of LOAD segments.
@@ -39,10 +45,10 @@ bool punchHolesInElf64(const char* filePath, uint64_t offset);
bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
/*
- * This function reads program headers from ELF file. ELF can be specified with file path directly
- * or it should be at offset inside Apk. Program headers passed to function is populated.
+ * This function reads program headers from 64 bit ELF file. ELF can be specified with file path
+ * directly or it should be at offset inside Apk. Program headers passed to function is populated.
*/
-bool getLoadSegmentPhdrs(const char* filePath, const uint64_t offset,
- std::vector<Elf64_Phdr>& programHeaders);
+read_elf_status_t getLoadSegmentPhdrs(const char* filePath, const uint64_t offset,
+ std::vector<Elf64_Phdr>& programHeaders);
} // namespace android \ No newline at end of file
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 14132e61ff0e..7cf523f18a90 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -640,7 +640,17 @@ com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) {
std::vector<Elf64_Phdr> programHeaders;
- if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) {
+ read_elf_status_t status = getLoadSegmentPhdrs(fileName, offset, programHeaders);
+ // Ignore the ELFs which are not 64 bit.
+ if (status == ELF_IS_NOT_64_BIT) {
+ ALOGW("ELF file is not 64 Bit");
+ // PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED is equivalent of skipping the current file.
+ // on return, flag is OR'ed with flags from other ELF files. If some app has 32 bit ELF in
+ // 64 bit directory, alignment of that ELF will be ignored.
+ return PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+ }
+
+ if (status == ELF_READ_ERROR) {
ALOGE("Failed to read program headers from ELF file.");
return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8c7b335e6e86..42f444419765 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1833,6 +1833,15 @@ static void BindMountSyspropOverride(fail_fn_t fail_fn, JNIEnv* env) {
ReloadBuildJavaConstants(env);
}
+static void MountInitOverride(fail_fn_t fail_fn, JNIEnv* env) {
+ const char* init_etc_dir = "/system/etc/init";
+
+ if (TEMP_FAILURE_RETRY(mount("tmpfs", init_etc_dir, "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC,
+ "uid=0,gid=0,mode=0751")) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount tmpfs %s: %s", init_etc_dir, strerror(errno)));
+ }
+}
+
static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid,
const char* dir_name, const char* package, fail_fn_t fail_fn) {
bool hasSdcardFs = IsSdcardfsUsed();
@@ -1954,6 +1963,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
if (mount_sysprop_overrides) {
BindMountSyspropOverride(fail_fn, env);
+ MountInitOverride(fail_fn, env);
}
// If this zygote isn't root, it won't be able to create a process group,
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index 75330be2624d..1b3b14da49d5 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -300,7 +300,7 @@ not_valid_surface:
}
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(_env, native_window));
- window = new Surface(producer, true);
+ window = sp<Surface>::make(producer, true);
if (window == NULL)
goto not_valid_surface;
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 746740b0248b..1e8da730aff8 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -280,12 +280,18 @@ static string getJavaProperty(JNIEnv* env, const char* property_name,
return string(chars.c_str());
}
-static void loadIcuData(string icuPath) {
+static void loadIcuData(JNIEnv* env, string icuPath) {
void* addr = mmapFile(icuPath.c_str());
+ if (addr == nullptr) {
+ jniThrowRuntimeException(env, "Failed to map the ICU data file.");
+ }
UErrorCode err = U_ZERO_ERROR;
udata_setCommonData(addr, &err);
if (err != U_ZERO_ERROR) {
- ALOGE("Unable to load ICU data\n");
+ jniThrowRuntimeException(env,
+ format("udata_setCommonData failed with error code {}",
+ u_errorName(err))
+ .c_str());
}
}
@@ -296,12 +302,12 @@ static void loadIcuData() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
string icuPath = base::GetProperty("ro.icu.data.path", "");
if (!icuPath.empty()) {
- loadIcuData(icuPath);
+ loadIcuData(env, icuPath);
} else {
// fallback to read from java.lang.System.getProperty
string icuPathFromJava = getJavaProperty(env, "icu.data.path");
if (!icuPathFromJava.empty()) {
- loadIcuData(icuPathFromJava);
+ loadIcuData(env, icuPathFromJava);
}
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 59e01bfa0c4b..9df351fa39c4 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -35,7 +35,6 @@ import "frameworks/base/core/proto/android/os/system_properties.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
import "frameworks/base/core/proto/android/server/alarm/alarmmanagerservice.proto";
-import "frameworks/base/core/proto/android/server/bluetooth_manager_service.proto";
import "frameworks/base/core/proto/android/server/fingerprint.proto";
import "frameworks/base/core/proto/android/server/jobscheduler.proto";
import "frameworks/base/core/proto/android/server/location/context_hub.proto";
@@ -483,10 +482,8 @@ message IncidentProto {
(section).args = "connmetrics --proto"
];
- optional com.android.server.BluetoothManagerServiceDumpProto bluetooth_manager = 3050 [
- (section).type = SECTION_DUMPSYS,
- (section).args = "bluetooth_manager --proto"
- ];
+ // Deprecated BluetoothManagerServiceDumpProto
+ reserved 3050;
optional com.android.server.location.ContextHubServiceProto context_hub = 3051 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 8de77469d170..5831a0bfc39e 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -112,7 +112,8 @@ message SecureSettingsProto {
optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto autoclick_panel_position = 64 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto autoclick_revert_to_left_click = 65 [ (android.privacy).dest = DEST_AUTOMATIC ];
-
+ // Setting for accessibility magnification for cursor following mode.
+ optional SettingProto accessibility_magnification_cursor_following_mode = 66 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
@@ -238,6 +239,10 @@ message SecureSettingsProto {
repeated SettingProto completed_categories = 15;
optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto adaptive_connectivity_enabled = 84 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto adaptive_connectivity_wifi_enabled = 105 [ (android.privacy).dest =
+ DEST_AUTOMATIC ];
+ optional SettingProto adaptive_connectivity_mobile_network_enabled = 106 [ (android.privacy)
+ .dest = DEST_AUTOMATIC ];
message Controls {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -295,13 +300,6 @@ message SecureSettingsProto {
optional SettingProto enhanced_voice_privacy_enabled = 23 [ (android.privacy).dest = DEST_AUTOMATIC ];
- message EvenDimmer {
- optional SettingProto even_dimmer_activated = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto even_dimmer_min_nits = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
- }
-
- optional EvenDimmer even_dimmer = 98;
-
optional SettingProto font_weight_adjustment = 85 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Gesture {
@@ -747,5 +745,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 105;
+ // Next tag = 107;
}
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 325790c22fce..8393f8b4db61 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -290,7 +290,16 @@ message SystemSettingsProto {
optional SettingProto apply_ramping_ringer = 35 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ message Display {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto cv_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional Display display = 39;
+
+
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 39;
+ // Next tag = 40;
}
diff --git a/core/proto/android/server/Android.bp b/core/proto/android/server/Android.bp
deleted file mode 100644
index 362daa73ff14..000000000000
--- a/core/proto/android/server/Android.bp
+++ /dev/null
@@ -1,28 +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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "srcs_bluetooth_manager_service_proto",
- srcs: [
- "bluetooth_manager_service.proto",
- ],
- visibility: ["//packages/modules/Bluetooth:__subpackages__"],
-}
-
diff --git a/core/proto/android/server/bluetooth_manager_service.proto b/core/proto/android/server/bluetooth_manager_service.proto
deleted file mode 100644
index c33f66a9aeca..000000000000
--- a/core/proto/android/server/bluetooth_manager_service.proto
+++ /dev/null
@@ -1,49 +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.
- */
-
-syntax = "proto2";
-package com.android.server;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/bluetooth/enums.proto";
-
-option java_multiple_files = true;
-
-message BluetoothManagerServiceDumpProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
- message ActiveLog {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int64 timestamp_ms = 1;
- optional bool enable = 2;
- optional string package_name = 3;
- optional .android.bluetooth.EnableDisableReasonEnum reason = 4;
- }
-
- optional bool enabled = 1;
- optional int32 state = 2;
- optional string state_name = 3;
- optional string address = 4 [(.android.privacy).dest = DEST_EXPLICIT];
- optional string name = 5 [(.android.privacy).dest = DEST_EXPLICIT];
- optional int64 last_enabled_time_ms = 6;
- optional int64 curr_timestamp_ms = 7;
- repeated ActiveLog active_logs = 8;
- optional int32 num_crashes = 9;
- optional bool crash_log_maxed = 10;
- repeated int64 crash_timestamps_ms = 11;
- optional int32 num_ble_apps = 12;
- repeated string ble_app_package_names = 13;
-} \ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index a673ad7dfb34..5820c8e947c2 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -216,10 +216,10 @@ message DisplayContentProto {
optional .com.android.server.wm.IdentifierProto resumed_activity = 24;
repeated TaskProto tasks = 25 [deprecated=true];
optional bool display_ready = 26;
- optional WindowStateProto input_method_target = 27;
- optional WindowStateProto input_method_input_target = 28;
- optional WindowStateProto input_method_control_target = 29;
- optional WindowStateProto current_focus = 30;
+ optional WindowStateProto input_method_target = 27 [deprecated = true];
+ optional WindowStateProto input_method_input_target = 28 [deprecated = true];
+ optional WindowStateProto input_method_control_target = 29 [deprecated = true];
+ optional WindowStateProto current_focus = 30 [deprecated = true];
optional ImeInsetsSourceProviderProto ime_insets_source_provider = 31;
optional bool can_show_ime = 32 [deprecated=true];
@@ -231,6 +231,10 @@ message DisplayContentProto {
repeated string sleep_tokens = 37;
repeated .android.graphics.RectProto keep_clear_areas = 38;
optional int32 min_size_of_resizeable_task_dp = 39;
+ optional IdentifierProto input_method_layering_target_identifier = 40;
+ optional IdentifierProto input_method_input_target_identifier = 41;
+ optional IdentifierProto input_method_control_target_identifier = 42;
+ optional IdentifierProto current_focus_identifier = 43;
}
/* represents DisplayArea object */
@@ -590,9 +594,9 @@ message InsetsSourceProviderProto {
optional .android.graphics.RectProto frame = 2;
optional .android.view.InsetsSourceControlProto fake_control = 3;
optional .android.view.InsetsSourceControlProto control = 4;
- optional WindowStateProto control_target = 5;
- optional WindowStateProto pending_control_target = 6;
- optional WindowStateProto fake_control_target = 7;
+ optional WindowStateProto control_target = 5 [deprecated = true];
+ optional WindowStateProto pending_control_target = 6 [deprecated = true];
+ optional WindowStateProto fake_control_target = 7 [deprecated = true];
optional .android.view.SurfaceControlProto captured_leash = 8;
optional .android.graphics.RectProto ime_overridden_frame = 9 [deprecated=true];
optional bool is_leash_ready_for_dispatching = 10;
@@ -601,15 +605,21 @@ message InsetsSourceProviderProto {
optional bool seamless_rotating = 13;
optional int64 finish_seamless_rotate_frame_number = 14;
optional bool controllable = 15;
- optional WindowStateProto source_window_state = 16;
+ optional WindowStateProto source_window_state = 16 [deprecated = true];
+
+ optional IdentifierProto control_target_identifier = 17;
+ optional IdentifierProto pending_control_target_identifier = 18;
+ optional IdentifierProto fake_control_target_identifier = 19;
+ optional IdentifierProto source_window_state_identifier = 20;
}
message ImeInsetsSourceProviderProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional InsetsSourceProviderProto insets_source_provider = 1;
- optional WindowStateProto ime_target_from_ime = 2;
+ optional WindowStateProto ime_target_from_ime = 2 [deprecated = true];
optional bool is_ime_layout_drawn = 3 [deprecated=true];
+ optional IdentifierProto ime_target_from_ime_identifier = 4;
}
message BackNavigationProto {
diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_down.xml b/core/res/res/drawable/accessibility_autoclick_scroll_down.xml
new file mode 100644
index 000000000000..13f1ba0dafe8
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_scroll_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M12,20l8,-8h-5V4h-6v8H4z"/>
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_exit.xml b/core/res/res/drawable/accessibility_autoclick_scroll_exit.xml
new file mode 100644
index 000000000000..e53301f25d65
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_scroll_exit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_left.xml b/core/res/res/drawable/accessibility_autoclick_scroll_left.xml
new file mode 100644
index 000000000000..39475bc689d2
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_scroll_left.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M4,12l8,8v-5h8v-6h-8V4z"/>
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_right.xml b/core/res/res/drawable/accessibility_autoclick_scroll_right.xml
new file mode 100644
index 000000000000..bbd7b2a79459
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_scroll_right.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M20,12l-8,-8v5H4v6h8v5z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml b/core/res/res/drawable/accessibility_autoclick_scroll_up.xml
index 495fbb893eac..2e2c245effa1 100644
--- a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
+++ b/core/res/res/drawable/accessibility_autoclick_scroll_up.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
+<!-- Copyright 2025 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,13 +14,12 @@
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="24dp"/>
- <gradient
- android:angle="0"
- android:startColor="#00000000"
- android:endColor="#ff000000"
- android:type="linear" />
-</shape>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M12,4L4,12h5v8h6v-8h5z"/>
+</vector>
diff --git a/core/res/res/layout/accessibility_autoclick_scroll_panel.xml b/core/res/res/layout/accessibility_autoclick_scroll_panel.xml
new file mode 100644
index 000000000000..1e093bbc7c35
--- /dev/null
+++ b/core/res/res/layout/accessibility_autoclick_scroll_panel.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/accessibility_autoclick_scroll_panel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/accessibility_autoclick_type_panel_rounded_background"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <!-- Up arrow -->
+ <LinearLayout
+ android:id="@+id/scroll_up_layout"
+ style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/accessibility_autoclick_type_panel_button_spacing">
+ <ImageButton
+ android:id="@+id/scroll_up"
+ style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_scroll_up"
+ android:src="@drawable/accessibility_autoclick_scroll_up" />
+ </LinearLayout>
+
+ <!-- Middle row: Left, Exit, Right -->
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:id="@+id/scroll_left_layout"
+ style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing">
+ <ImageButton
+ android:id="@+id/scroll_left"
+ style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_scroll_left"
+ android:src="@drawable/accessibility_autoclick_scroll_left" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/scroll_exit_layout"
+ style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing">
+ <ImageButton
+ android:id="@+id/scroll_exit"
+ style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_scroll_exit"
+ android:src="@drawable/ic_close" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/scroll_right_layout"
+ style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle">
+ <ImageButton
+ android:id="@+id/scroll_right"
+ style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_scroll_right"
+ android:src="@drawable/accessibility_autoclick_scroll_right" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <!-- Down arrow -->
+ <LinearLayout
+ android:id="@+id/scroll_down_layout"
+ style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="@dimen/accessibility_autoclick_type_panel_button_spacing">
+ <ImageButton
+ android:id="@+id/scroll_down"
+ style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_scroll_down"
+ android:src="@drawable/accessibility_autoclick_scroll_down" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml
index 902ef7fc38e8..615af6fb16f8 100644
--- a/core/res/res/layout/accessibility_autoclick_type_panel.xml
+++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml
@@ -49,7 +49,8 @@
android:id="@+id/accessibility_autoclick_drag_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_drag"
- android:src="@drawable/accessibility_autoclick_drag" />
+ android:src="@drawable/accessibility_autoclick_drag"
+ android:clickable="false" />
</LinearLayout>
<LinearLayout
@@ -60,7 +61,8 @@
android:id="@+id/accessibility_autoclick_double_click_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_double_click"
- android:src="@drawable/accessibility_autoclick_double_click" />
+ android:src="@drawable/accessibility_autoclick_double_click"
+ android:clickable="false" />
</LinearLayout>
<LinearLayout
@@ -71,7 +73,8 @@
android:id="@+id/accessibility_autoclick_right_click_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_right_click"
- android:src="@drawable/accessibility_autoclick_right_click" />
+ android:src="@drawable/accessibility_autoclick_right_click"
+ android:clickable="false" />
</LinearLayout>
<LinearLayout
@@ -82,7 +85,8 @@
android:id="@+id/accessibility_autoclick_scroll_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_scroll"
- android:src="@drawable/accessibility_autoclick_scroll" />
+ android:src="@drawable/accessibility_autoclick_scroll"
+ android:clickable="false" />
</LinearLayout>
<LinearLayout
@@ -93,7 +97,8 @@
android:id="@+id/accessibility_autoclick_left_click_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_left_click"
- android:src="@drawable/accessibility_autoclick_left_click" />
+ android:src="@drawable/accessibility_autoclick_left_click"
+ android:clickable="false" />
</LinearLayout>
</LinearLayout>
@@ -114,7 +119,8 @@
android:id="@+id/accessibility_autoclick_pause_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_pause"
- android:src="@drawable/accessibility_autoclick_pause" />
+ android:src="@drawable/accessibility_autoclick_pause"
+ android:clickable="false" />
</LinearLayout>
<LinearLayout
@@ -125,7 +131,8 @@
android:id="@+id/accessibility_autoclick_position_button"
style="@style/AccessibilityAutoclickPanelImageButtonStyle"
android:contentDescription="@string/accessibility_autoclick_position"
- android:src="@drawable/accessibility_autoclick_position" />
+ android:src="@drawable/accessibility_autoclick_position"
+ android:clickable="false" />
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/notification_2025_action_list.xml b/core/res/res/layout/notification_2025_action_list.xml
new file mode 100644
index 000000000000..6c07ec1a7c7b
--- /dev/null
+++ b/core/res/res/layout/notification_2025_action_list.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/actions_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/notification_2025_action_list_margin_bottom"
+ android:minHeight="@dimen/notification_2025_action_list_min_height"
+ >
+
+ <LinearLayout
+ android:id="@+id/actions_container_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:layout_gravity="bottom"
+ android:orientation="horizontal"
+ android:background="@color/notification_action_list_background_color"
+ >
+
+ <com.android.internal.widget.NotificationActionListLayout
+ android:id="@+id/actions"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_2025_action_list_height"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:visibility="gone"
+ >
+ <!-- actions will be added here -->
+ </com.android.internal.widget.NotificationActionListLayout>
+
+ <!--
+ This nested linear layout exists to ensure that if the neither of the contained
+ actions is visible we have some minimum padding at the end of the actions is present,
+ then there will be 12dp of padding at the end of the actions list.
+
+ The end padding exists to match the bottom margin of the actions, for symmetry when the icon
+ is shown in the corner of the notification.
+ -->
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingEnd="@dimen/notification_2025_action_list_margin_bottom"
+ android:minWidth="@dimen/snooze_and_bubble_gone_padding_end"
+ >
+ <ImageView
+ android:id="@+id/snooze_button"
+ android:layout_width="@dimen/notification_2025_actions_icon_size"
+ android:layout_height="@dimen/notification_2025_actions_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:visibility="gone"
+ android:scaleType="centerInside"
+ />
+
+ <ImageView
+ android:id="@+id/bubble_button"
+ android:layout_width="@dimen/notification_2025_actions_icon_size"
+ android:layout_height="@dimen/notification_2025_actions_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:visibility="gone"
+ android:scaleType="centerInside"
+ />
+ </LinearLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_conversation_icon_container.xml b/core/res/res/layout/notification_2025_conversation_icon_container.xml
index 7ec2450ceb71..16c95009051c 100644
--- a/core/res/res/layout/notification_2025_conversation_icon_container.xml
+++ b/core/res/res/layout/notification_2025_conversation_icon_container.xml
@@ -29,7 +29,8 @@
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="@dimen/notification_2025_margin"
+ android:layout_marginHorizontal="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_gravity="top|center_horizontal"
diff --git a/core/res/res/layout/notification_2025_expand_button.xml b/core/res/res/layout/notification_2025_expand_button.xml
index 1c367544c90a..8ba844a4868b 100644
--- a/core/res/res/layout/notification_2025_expand_button.xml
+++ b/core/res/res/layout/notification_2025_expand_button.xml
@@ -15,6 +15,7 @@
-->
<!-- extends FrameLayout -->
+<!-- Note: The button's padding may be dynamically adjusted in code -->
<com.android.internal.widget.NotificationExpandButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/expand_button"
diff --git a/core/res/res/layout/notification_2025_reply_history_container.xml b/core/res/res/layout/notification_2025_reply_history_container.xml
index 6923b59f34dc..256f7b89c784 100644
--- a/core/res/res/layout/notification_2025_reply_history_container.xml
+++ b/core/res/res/layout/notification_2025_reply_history_container.xml
@@ -28,16 +28,16 @@
android:layout_width="match_parent"
android:layout_height="1dip"
android:id="@+id/action_divider"
- android:layout_marginTop="@dimen/notification_content_margin"
- android:layout_marginBottom="@dimen/notification_content_margin"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginBottom="@dimen/notification_2025_margin"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:background="@drawable/notification_template_divider" />
<TextView
android:id="@+id/notification_material_reply_text_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:visibility="gone"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply"
android:singleLine="true" />
@@ -46,7 +46,7 @@
android:id="@+id/notification_material_reply_text_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:visibility="gone"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply"
android:singleLine="true" />
@@ -56,13 +56,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:layout_marginEnd="@dimen/notification_content_margin_end">
+ android:layout_marginEnd="@dimen/notification_2025_margin">
<TextView
android:id="@+id/notification_material_reply_text_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:layout_gravity="center"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply"
android:singleLine="true" />
diff --git a/core/res/res/layout/notification_2025_right_icon.xml b/core/res/res/layout/notification_2025_right_icon.xml
new file mode 100644
index 000000000000..24d381d10501
--- /dev/null
+++ b/core/res/res/layout/notification_2025_right_icon.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<!-- Large icon to be used in expanded notification layouts. -->
+<com.android.internal.widget.CachingIconView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="@dimen/notification_2025_right_icon_expanded_margin_end"
+ android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:maxDrawableWidth="@dimen/notification_right_icon_size"
+ android:maxDrawableHeight="@dimen/notification_right_icon_size"
+ />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index d29b7af9e24e..57c89b91cff7 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -66,14 +66,16 @@
android:orientation="horizontal"
>
+ <!--
+ We use a smaller vertical margin than usual, to allow the content of custom views to
+ grow up to 48dp height when needed in collapsed notifications.
+ -->
<LinearLayout
android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
- android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginVertical="@dimen/notification_2025_reduced_margin"
android:orientation="vertical"
>
@@ -81,6 +83,7 @@
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_2025_additional_margin"
android:minHeight="@dimen/notification_2025_content_min_height"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
@@ -118,17 +121,10 @@
<com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_2025_additional_margin"
android:minHeight="@dimen/notification_headerless_line_height"
>
- <!-- This is the simplest way to keep this text vertically centered without
- gravity="center_vertical" which causes jumpiness in expansion animations. -->
- <include
- layout="@layout/notification_2025_text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="0dp"
- />
+ <include layout="@layout/notification_2025_text" />
</com.android.internal.widget.NotificationVanishingFrameLayout>
<include
@@ -146,9 +142,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -161,7 +156,7 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
index ee691e4d6894..c57196e9c9e8 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -32,11 +32,11 @@
android:orientation="vertical"
>
- <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
+ android:layout_weight="1"
>
<ImageView
@@ -77,9 +77,7 @@
android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginTop="@dimen/notification_2025_margin"
android:clipChildren="false"
android:orientation="vertical"
@@ -128,15 +126,7 @@
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_headerless_line_height"
>
- <!-- This is the simplest way to keep this text vertically centered without
- gravity="center_vertical" which causes jumpiness in expansion animations. -->
- <include
- layout="@layout/notification_2025_text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="0dp"
- />
+ <include layout="@layout/notification_2025_text" />
</com.android.internal.widget.NotificationVanishingFrameLayout>
</LinearLayout>
@@ -147,9 +137,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -160,7 +149,7 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
@@ -179,23 +168,15 @@
android:layout_height="@dimen/notification_close_button_size"
android:layout_gravity="top|end" />
- </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+ </FrameLayout>
- <LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ <include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="-20dp"
- android:clipChildren="false"
- android:orientation="vertical">
- <include layout="@layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_material_action_list" />
- </LinearLayout>
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_2025_margin" />
+ <include layout="@layout/notification_2025_action_list" />
</LinearLayout>
</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
index f80411103501..1c6fcb50dca5 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -32,12 +32,11 @@
android:orientation="vertical"
>
-
- <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
+ android:layout_weight="1"
>
<ImageView
@@ -78,17 +77,11 @@
android:clipChildren="false"
>
- <!--
- NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
- have the id/notification_headerless_view_column, as that is used for modifying
- vertical margins to accommodate the single-line state that base supports
- -->
<LinearLayout
+ android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginTop="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:clipChildren="false"
@@ -149,9 +142,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:forceHasOverlappingRendering="false"
android:spacing="0dp"
android:clipChildren="false"
@@ -163,9 +155,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -176,7 +167,7 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
@@ -195,22 +186,15 @@
android:layout_height="@dimen/notification_close_button_size"
android:layout_gravity="top|end" />
- </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+ </FrameLayout>
- <LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ <include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="-20dp"
- android:clipChildren="false"
- android:orientation="vertical">
- <include layout="@layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_material_action_list" />
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_2025_margin" />
+ <include layout="@layout/notification_2025_action_list" />
+
</LinearLayout>
-</LinearLayout>
</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 5beab508aecf..de82f9feb512 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -68,14 +68,16 @@
android:orientation="horizontal"
>
+ <!--
+ We use a smaller vertical margin than usual, to allow the content of custom views to
+ grow up to 48dp height when needed in collapsed notifications.
+ -->
<LinearLayout
android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
- android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginVertical="@dimen/notification_2025_reduced_margin"
android:orientation="vertical"
>
@@ -83,6 +85,7 @@
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_2025_additional_margin"
android:minHeight="@dimen/notification_headerless_line_height"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
@@ -119,17 +122,10 @@
<com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
+ android:layout_marginBottom="@dimen/notification_2025_additional_margin"
android:layout_height="@dimen/notification_headerless_line_height"
>
- <!-- This is the simplest way to keep this text vertically centered without
- gravity="center_vertical" which causes jumpiness in expansion animations. -->
- <include
- layout="@layout/notification_template_text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="0dp"
- />
+ <include layout="@layout/notification_2025_text" />
</com.android.internal.widget.NotificationVanishingFrameLayout>
<include
@@ -147,9 +143,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -182,7 +177,7 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index d7c3263904d4..8e2cb23f1198 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -35,11 +35,11 @@
>
- <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
+ android:layout_weight="1"
>
<ImageView
@@ -60,7 +60,8 @@
android:layout_width="@dimen/notification_2025_icon_circle_size"
android:layout_height="@dimen/notification_2025_icon_circle_size"
android:layout_alignParentStart="true"
- android:layout_margin="@dimen/notification_2025_margin"
+ android:layout_marginHorizontal="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
android:background="@drawable/notification_icon_circle"
android:padding="@dimen/notification_2025_icon_circle_padding"
/>
@@ -88,17 +89,11 @@
android:clipChildren="false"
>
- <!--
- NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
- have the id/notification_headerless_view_column, as that is used for modifying
- vertical margins to accommodate the single-line state that base supports
- -->
<LinearLayout
+ android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginTop="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:clipChildren="false"
@@ -159,9 +154,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:forceHasOverlappingRendering="false"
android:spacing="0dp"
android:clipChildren="false"
@@ -173,9 +167,8 @@
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin"
+ android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -186,7 +179,7 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
@@ -205,22 +198,15 @@
android:layout_height="@dimen/notification_close_button_size"
android:layout_gravity="top|end" />
- </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+ </FrameLayout>
- <LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ <include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="-20dp"
- android:clipChildren="false"
- android:orientation="vertical">
- <include layout="@layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
- android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_material_action_list" />
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_2025_margin" />
+ <include layout="@layout/notification_2025_action_list" />
+
</LinearLayout>
-</LinearLayout>
</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
index 52bc7b8ea3bb..b32a7788c433 100644
--- a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
@@ -76,7 +76,7 @@
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
android:layout_width="wrap_content"
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
index cf9ff6bef6f8..268396f11cab 100644
--- a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
@@ -96,14 +96,14 @@
<FrameLayout
android:id="@+id/reply_action_container"
android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_action_list_height"
+ android:layout_height="@dimen/notification_2025_action_list_height"
android:gravity="center_vertical"
android:orientation="horizontal" />
<FrameLayout
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:minWidth="@dimen/notification_2025_margin"
>
<include layout="@layout/notification_2025_expand_button"
android:layout_width="wrap_content"
diff --git a/core/res/res/layout/notification_2025_template_expanded_base.xml b/core/res/res/layout/notification_2025_template_expanded_base.xml
index e12db2783191..287110766dc7 100644
--- a/core/res/res/layout/notification_2025_template_expanded_base.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_base.xml
@@ -24,10 +24,8 @@
>
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
@@ -47,7 +45,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
>
@@ -63,7 +61,7 @@
/>
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</FrameLayout>
<ViewStub
@@ -78,10 +76,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
/>
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
index fac9d1c47f41..ead6d4cbc034 100644
--- a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
@@ -25,10 +25,9 @@
<include layout="@layout/notification_2025_template_header" />
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
@@ -43,7 +42,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
>
@@ -67,7 +66,7 @@
android:layout_weight="1"
android:layout_marginTop="13dp"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:background="@drawable/notification_big_picture_outline"
android:clipToOutline="true"
android:scaleType="centerCrop"
@@ -85,10 +84,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
/>
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
index 4a807cb674c6..de5e71d2015f 100644
--- a/core/res/res/layout/notification_2025_template_expanded_big_text.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
@@ -26,11 +26,9 @@
<include layout="@layout/notification_2025_template_header" />
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
- android:layout_marginBottom="@dimen/notification_content_margin"
android:clipToPadding="false"
android:orientation="vertical"
>
@@ -43,7 +41,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingStart="@dimen/notification_2025_content_margin_start"
- android:paddingEnd="@dimen/notification_content_margin_end"
+ android:paddingEnd="@dimen/notification_2025_margin"
android:clipToPadding="false"
android:orientation="vertical"
android:layout_weight="1"
@@ -84,12 +82,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
/>
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
index bbc29664d594..c096bc8a4d15 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -30,7 +30,6 @@
<include layout="@layout/notification_2025_conversation_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
@@ -46,7 +45,7 @@
android:layout_gravity="top"
android:layout_weight="1"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
android:clipChildren="false"
>
@@ -60,14 +59,14 @@
<include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ android:layout_marginEnd="@dimen/notification_2025_margin" />
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
index d7e8bb3b6da2..6eea8cc93aeb 100644
--- a/core/res/res/layout/notification_2025_template_expanded_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
@@ -29,7 +29,6 @@
<include layout="@layout/notification_2025_conversation_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
@@ -44,7 +43,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
android:clipChildren="false"
>
@@ -62,14 +61,14 @@
<include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ android:layout_marginEnd="@dimen/notification_2025_margin" />
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
index ccab02e312cc..327cd7ac71bb 100644
--- a/core/res/res/layout/notification_2025_template_expanded_inbox.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
@@ -24,7 +24,6 @@
>
<include layout="@layout/notification_2025_template_header" />
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
@@ -39,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingStart="@dimen/notification_2025_content_margin_start"
- android:paddingEnd="@dimen/notification_content_margin_end"
+ android:paddingEnd="@dimen/notification_2025_margin"
android:layout_weight="1"
android:clipToPadding="false"
android:orientation="vertical"
@@ -126,9 +125,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="@dimen/notification_content_margin" />
- <include layout="@layout/notification_material_action_list" />
+ android:layout_marginEnd="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin" />
+ <include layout="@layout/notification_2025_action_list" />
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml
index e90ab792581f..565a558a5115 100644
--- a/core/res/res/layout/notification_2025_template_expanded_media.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_media.xml
@@ -41,7 +41,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1"/>
@@ -53,7 +53,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_media_actions_margin_start"
- android:minHeight="@dimen/notification_content_margin"
+ android:minHeight="@dimen/notification_2025_margin"
>
<!-- Nesting in FrameLayout is required to ensure that the marginStart actually applies
@@ -99,6 +99,6 @@
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
index 20abfee6a4b6..df48479e5ec3 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -29,7 +29,6 @@
<include layout="@layout/notification_2025_template_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
@@ -44,7 +43,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
android:clipChildren="false"
>
@@ -62,14 +61,14 @@
<include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ android:layout_marginEnd="@dimen/notification_2025_margin" />
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml
index 87ded8975cb0..b929b9ed20b7 100644
--- a/core/res/res/layout/notification_2025_template_expanded_progress.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml
@@ -25,10 +25,8 @@
>
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
@@ -48,7 +46,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
android:orientation="vertical"
>
@@ -99,7 +97,7 @@
</LinearLayout>
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</FrameLayout>
<ViewStub
@@ -114,10 +112,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
/>
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</LinearLayout>
</FrameLayout> \ No newline at end of file
diff --git a/core/res/res/layout/notification_2025_template_heads_up_base.xml b/core/res/res/layout/notification_2025_template_heads_up_base.xml
index 084ec7daa683..e416c5054e00 100644
--- a/core/res/res/layout/notification_2025_template_heads_up_base.xml
+++ b/core/res/res/layout/notification_2025_template_heads_up_base.xml
@@ -56,11 +56,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginEnd="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
/>
- <include layout="@layout/notification_material_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_text.xml b/core/res/res/layout/notification_2025_text.xml
index 474f6d2099c6..a725de44b0bf 100644
--- a/core/res/res/layout/notification_2025_text.xml
+++ b/core/res/res/layout/notification_2025_text.xml
@@ -13,14 +13,16 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
+<!-- Note that we prefer layout_gravity="center_vertical" over gravity="center_vertical", since the
+ latter can cause jumpiness in expansion animations. -->
<com.android.internal.widget.ImageFloatingTextView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.DeviceDefault.Notification.Text"
android:id="@+id/text"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="@dimen/notification_text_margin_top"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
android:ellipsize="end"
android:fadingEdge="horizontal"
android:gravity="top"
diff --git a/core/res/res/layout/notification_template_notification_progress_bar.xml b/core/res/res/layout/notification_template_notification_progress_bar.xml
index 35748962cfb2..8511d38718d1 100644
--- a/core/res/res/layout/notification_template_notification_progress_bar.xml
+++ b/core/res/res/layout/notification_template_notification_progress_bar.xml
@@ -20,4 +20,5 @@
android:layout_width="match_parent"
android:layout_height="@dimen/notification_progress_tracker_height"
style="@style/Widget.Material.Notification.NotificationProgressBar"
+ android:accessibilityLiveRegion="polite"
/>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index b6b5d8ba2d2f..59ac5c68f9a5 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Eenhandmodus"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra donker"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Gehoortoestelle"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Outoklik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ontkoppel"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Gekoppel"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktief"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Jy kan jou gehoortoestel en mikrofoon vir handvrye oproepe gebruik. Dit skakel net jou mikrofoon tydens die oproep oor."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Skakel oor"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Instellings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Huidige gebruiker <xliff:g id="NAME">%1$s</xliff:g> ."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Skakel tans oor na <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Meld <xliff:g id="NAME">%1$s</xliff:g> tans af …"</string>
<string name="owner_name" msgid="8713560351570795743">"Eienaar"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Vra ontsluitpatroon voordat jy ontspeld"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vra wagwoord voordat jy ontspeld"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Deur jou admin geïnstalleer.\nGaan na instellings om toegestaande toestemmings te sien"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Opgedateer deur jou administrateur"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Uitgevee deur jou administrateur"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Batterybespaarder skakel Donkertema aan en beperk of skakel agtergrondaktiwiteit, sommige visuele effekte, sekere kenmerke en sommige netwerkverbindings af"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Rollees"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Onderbreek"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posisie"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> is in die BEPERK-groep geplaas"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"het \'n prent gestuur"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Werk 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Toets"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Gemeenskaplik"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Werkprofiel"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privaat ruimte"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 29b153b23d3d..169e0208cf34 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"የአንድ እጅ ሁነታ"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ተጨማሪ ደብዛዛ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"የመስሚያ መሣሪያዎች"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ራስ-ሰር ጠቅ ማድረግ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ግንኙነት ተቋርጧል"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"ተገናኝቷል"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ገቢር"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"የእርስዎን መስሚያ አጋዥ መሣሪያ ማይክሮፎን ለነጻ እጅ መደወል መጠቀም ይችላሉ። ይህ በጥሪው ወቅት ማይክሮፎንዎን ብቻ ይቀይራል።"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ቀይር"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ቅንብሮች"</string>
- <string name="user_switched" msgid="7249833311585228097">"የአሁኑ ተጠቃሚ <xliff:g id="NAME">%1$s</xliff:g>።"</string>
<string name="user_switching_message" msgid="1912993630661332336">"ወደ <xliff:g id="NAME">%1$s</xliff:g> በመቀየር ላይ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> በማውጣት ላይ…"</string>
<string name="owner_name" msgid="8713560351570795743">"ባለቤት"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ከመንቀል በፊት የማስከፈቻ ሥርዓተ-ጥለት ጠይቅ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ከመንቀል በፊት የይለፍ ቃል ጠይቅ"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"በአስተዳዳሪዎ ተጭኗል።\nየተፈቀዱ ፍቃዶችን ለማየት ወደ ቅንብሮች ይሂዱ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"በእርስዎ አስተዳዳሪ ተዘምኗል"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"በእርስዎ አስተዳዳሪ ተሰርዟል"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"እሺ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ባትሪ ቆጣቢ ጠቆር ያለ ገጽታን ያበራል እና የጀርባ እንቅስቃሴን፣ አንዳንድ ዕይታዊ ውጤቶችን፣ አንዳንድ ባህሪዎችን፣ እና አንዳንድ የአውታረ መረብ ግንኙነቶችን ይገድባል ወይም ያጠፋል።"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"ሸብልል"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ባለበት አቁም"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"አቀማመጥ"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ወደ የRESTRICTED ባልዲ ተከትቷል"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>፦"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"አንድ ምስል ልከዋል"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ሥራ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ሙከራ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"የጋራ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"የሥራ መገለጫ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"የግል ቦታ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"አባዛ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 08d0af5f3a6f..1d2bd776b505 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1807,8 +1807,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"وضع \"التصفح بيد واحدة\""</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"زيادة تعتيم الشاشة"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"سماعات الأذن الطبية"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"النقر التلقائي"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"غير متّصل"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"متّصل"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"متّصل حاليًا"</string>
@@ -1829,7 +1828,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"يمكنك استخدام ميكروفون سماعة الأذن الطبية لإجراء مكالمات بدون لمس الجهاز. يؤدي هذا الإجراء إلى تبديل الميكروفون فقط أثناء المكالمة."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"تبديل"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"الإعدادات"</string>
- <string name="user_switched" msgid="7249833311585228097">"المستخدم الحالي <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"جارٍ التبديل إلى <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"جارٍ الخروج <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"المالك"</string>
@@ -1971,7 +1969,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"طلب إدخال نقش فتح القفل قبل إزالة التثبيت"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"طلب إدخال كلمة المرور قبل إزالة التثبيت"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"تم التثبيت من قِبل المشرف.\nانتقِل إلى الإعدادات للاطّلاع على الأذونات الممنوحة"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"تم التحديث بواسطة المشرف"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"تم الحذف بواسطة المشرف"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"حسنًا"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"يؤدي استخدام ميزة \"توفير شحن البطارية\" إلى تفعيل وضع \"المظهر الداكن\" وتقييد أو إيقاف الأنشطة في الخلفية وبعض التأثيرات المرئية وميزات معيّنة وبعض اتصالات الشبكات."</string>
@@ -2279,6 +2278,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"الانتقال للأسفل أو للأعلى"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"إيقاف مؤقت"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"تعديل الموضع"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"تم وضع <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> في الحزمة \"محظورة\"."</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"هذا المستخدم أرسل صورة"</string>
@@ -2486,6 +2497,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ملف العمل 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ملف شخصي تجريبي"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"ملف شخصي مشترك"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ملف العمل"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"المساحة الخاصة"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"نسخة طبق الأصل"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 9eab313b1b94..606161c29f12 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"এখন হাতেৰে ব্যৱহাৰ কৰাৰ ম’ড"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"অতিৰিক্তভাৱে পোহৰ কমোৱাৰ সুবিধা"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"শুনাৰ ডিভাইচ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"স্বয়ংক্ৰিয়ভাৱে ক্লিক কৰাৰ সুবিধা"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"সংযোগ বিচ্ছিন্ন হ’ল"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"সংযোগ কৰা হ’ল"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"সক্ৰিয় হৈ আছে"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"আপুনি হেণ্ডছ্‌-ফ্ৰী কলিঙৰ বাবে আপোনাৰ শ্ৰৱণ যন্ত্ৰৰ মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰিব পাৰে। এইটোৱে কল চলি থাকোঁতে কেৱল আপোনাৰ মাইকটো সলনি কৰে।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"সলনি কৰক"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ছেটিং"</string>
- <string name="user_switched" msgid="7249833311585228097">"বৰ্তমানৰ ব্যৱহাৰকাৰী <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>লৈ সলনি কৰি থকা হৈছে…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ৰ পৰা লগ আউট কৰি থকা হৈছে…"</string>
<string name="owner_name" msgid="8713560351570795743">"গৰাকী"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"আনপিন কৰাৰ পূৰ্বে আনলক আৰ্হি দিবলৈ কওক"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"আনপিন কৰাৰ পূৰ্বে পাছৱৰ্ড দিবলৈ কওক"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"আপোনাৰ প্ৰশাসকে ইনষ্টল কৰিছে।\nপ্ৰদান কৰা অনুমতিসমূহ চাবলৈ ছেটিঙলৈ যাওক"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"আপোনাৰ প্ৰশাসকে আপেডট কৰিছে"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"আপোনাৰ প্ৰশাসকে মচিছে"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ঠিক আছে"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"বেটাৰী সঞ্চয়কাৰীয়ে গাঢ় ৰঙৰ থীম অন কৰে আৰু নেপথ্যৰ কাৰ্যকলাপ, কিছুমান ভিজুৱেল ইফেক্ট, নিৰ্দিষ্ট কিছুমান সুবিধা আৰু নেটৱৰ্কৰ সংযোগ সীমিত অথবা অফ কৰে।"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"স্ক্ৰ’ল কৰক"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"পজ কৰক"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"স্থান"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>ক সীমাবদ্ধ বাকেটটোত ৰখা হৈছে"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"এখন প্ৰতিচ্ছবি পঠিয়াইছে"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"কৰ্মস্থান ৩"</string>
<string name="profile_label_test" msgid="9168641926186071947">"পৰীক্ষা"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"শ্বেয়াৰ কৰা"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"কৰ্মস্থানৰ প্ৰ’ফাইল"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"প্ৰাইভেট স্পে’চ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ক্ল’ন"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 3aec62a8b8d5..904194f122b8 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Birəlli rejim"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Əlavə tündləşmə"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Eşitmə cihazları"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Avtomatik klikləmə"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Bağlantı kəsildi"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Qoşuldu"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivdir"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Səsli idarəetmə ilə zəng etmək üçün eşitmə aparatı mikrofonunuzdan istifadə edə bilərsiniz. Bu, yalnız zəng zamanı mikrofonu dəyişdirir."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Dəyişin"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ayarlar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Cari istifadəçi <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> adına keçirilir…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> çıxır..."</string>
<string name="owner_name" msgid="8713560351570795743">"Sahib"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Qrafik açar istənilsin"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ayırmadan öncə parol istənilsin"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Admin quraşdırıb.\nVerilən icazələrə baxmaq üçün ayarlara keçin"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Admin tərəfindən yeniləndi"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Admin tərəfindən silindi"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Enerjiyə Qənaət rejimi Tünd temanı aktivləşdirir, habelə arxa fon fəaliyyətini, bəzi vizual effektləri, müəyyən xüsusiyyətləri və bəzi şəbəkə bağlantılarını məhdudlaşdırır, yaxud söndürür."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Sürüşdürün"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Durdurun"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Mövqe"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> MƏHDUDLAŞDIRILMIŞ səbətinə yerləşdirilib"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"şəkil göndərdi"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"İş 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Kommunal"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"İş profili"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Məxfi sahə"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 47203a992934..e7fff52222f1 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jednom rukom"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatno zatamni"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušni aparati"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatski klik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Veza je prekinuta"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Možete da koristite mikrofon slušnog aparata za hendsfri pozivanje. Time se mikrofon menja samo tokom poziva."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Promeni"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Podešavanja"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuelni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljuje se <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži šablon za otključavanje pre otkačinjanja"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži lozinku pre otkačinjanja"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalirao je administrator.\nIdite u podešavanja da biste videli odobrene dozvole"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao je administrator"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao je administrator"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Potvrdi"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ušteda baterije uključuje tamnu temu i ograničava ili isključuje aktivnosti u pozadini, neke vizuelne efekte, određene funkcije i neke mrežne veze."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Skrolujte"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pauziraj"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozicija"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je dodat u segment OGRANIČENO"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslao/la sliku"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Posao 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Zajedničko"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Poslovni profil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatan prostor"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klonirano"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 8230f0c7739c..94739b353de9 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -159,7 +159,7 @@
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> праз <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
<string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не пераадрасоўваецца"</string>
<string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не пераадрасоўваецца"</string>
- <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Сеткавая бяспека"</string>
+ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Бяспека мабільнай сеткі"</string>
<string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шыфраванне, апавяшчэнні для незашыфраваных сетак"</string>
<string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Атрыманы доступ да ідэнтыфікатара прылады"</string>
<string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"У <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у сетцы паблізу быў запісаны ўнікальны ідэнтыфікатар вашай прылады (IMSI або IMEI) пры выкарыстанні SIM-карты <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Рэжым кіравання адной рукой"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Дадатковае памяншэнне яркасці"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слыхавыя апараты"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Аўтанацісканне"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Адключана"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Падключана"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Актыўная"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Вы можаце выкарыстоўваць мікрафон слыхавога апарата, каб размаўляць падчас званка без дапамогі рук. Будзе пераключаны толькі ваш мікрафон."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Пераключыцца"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Налады"</string>
- <string name="user_switched" msgid="7249833311585228097">"Бягучы карыстальнік <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Пераключэнне на карыстальніка \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> выходзіць з сістэмы…"</string>
<string name="owner_name" msgid="8713560351570795743">"Уладальнік"</string>
@@ -1969,7 +1967,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запытваць узор разблакіроўкі перад адмацаваннем"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запытваць пароль перад адмацаваннем"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Усталявана адміністратарам.\nКаб паглядзець дадзеныя дазволы, перайдзіце ў налады"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Абноўлены вашым адміністратарам"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Выдалены вашым адміністратарам"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"У рэжыме энергазберажэння ўключаецца цёмная тэма і выключаюцца ці абмяжоўваюцца дзеянні ў фонавым рэжыме, некаторыя візуальныя эфекты, пэўныя функцыі і падключэнні да сетак."</string>
@@ -2277,6 +2276,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Гартанне"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Прыпыніць"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Пазіцыя"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет \"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>\" дададзены ў АБМЕЖАВАНУЮ групу"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"адпраўлены відарыс"</string>
@@ -2484,6 +2495,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Працоўны 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Тэставы"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Супольны"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Працоўны профіль"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Прыватная прастора"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index d9471e68c8e1..2bd7d1d8ac60 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Работа с една ръка"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Допълнително затъмняване"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слухови апарати"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматично кликване"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Няма връзка"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Свързано"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Можете да използвате микрофона на слуховия си апарат за обаждания в режим „свободни ръце“. Микрофонът ще бъде включен само по време на обаждането."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Превключване"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Настройки"</string>
- <string name="user_switched" msgid="7249833311585228097">"Текущ потребител <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Превключва се към: <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> излиза…"</string>
<string name="owner_name" msgid="8713560351570795743">"Собственик"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запитване за фигура за отключване преди освобождаване"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запитване за парола преди освобождаване"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Инсталирано от администратора ви.\nОтворете настройките, за да прегледате предоставените разрешения"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Актуализирано от администратора ви"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Актуализирано от администратора ви.\nОтворете настройките, за да прегледате предоставените разрешения"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Изтрито от администратора ви"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Режимът за запазване на батерията включва тъмната тема и ограничава или изключва активността на заден план, някои визуални ефекти, определени функции и някои връзки с мрежата."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Превъртане"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Пауза"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Позиция"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакетът <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> е поставен в ОГРАНИЧЕНИЯ контейнер"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"изпратено изображение"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Служебни 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Тестване"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Общи"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Служебен потребителски профил"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Частно пространство"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клониране"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index cc05d28ca630..16a7e9aad04a 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -327,7 +327,7 @@
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"আপনার পরিচিতিগুলিতে অ্যাক্সেস"</string>
<string name="permgrouplab_location" msgid="1858277002233964394">"লোকেশন"</string>
<string name="permgroupdesc_location" msgid="1995955142118450685">"এই ডিভাইসের লোকেশন অ্যাক্সেস"</string>
- <string name="permgrouplab_calendar" msgid="6426860926123033230">"ক্যালেন্ডার"</string>
+ <string name="permgrouplab_calendar" msgid="6426860926123033230">"Calendar"</string>
<string name="permgroupdesc_calendar" msgid="6762751063361489379">"আপনার ক্যালেন্ডারে অ্যাক্সেস"</string>
<string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"এসএমএসগুলি পাঠাতে এবং দেখতে"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"এক হাতে ব্যবহার করার মোড"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"অতিরিক্ত কম উজ্জ্বলতা"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"হিয়ারিং ডিভাইস"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"অটোক্লিক"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ডিসকানেক্ট হয়ে গেছে"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"কানেক্ট করা হয়েছে"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"অ্যাক্টিভ"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"হ্যান্ডস-ফ্রি কলিংয়ের জন্য আপনার হিয়ারিং এডের মাইক্রোফোন ব্যবহার করতে পারবেন। এটি শুধুমাত্র কল চলাকালীন আপনার মাইক পরিবর্তন করে।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"পরিবর্তন করুন"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"সেটিংস"</string>
- <string name="user_switched" msgid="7249833311585228097">"বর্তমান ব্যবহারকারী <xliff:g id="NAME">%1$s</xliff:g>৷"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> প্রোফাইলে পাল্টানো হচ্ছে…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>কে লগ-আউট করা হচ্ছে..."</string>
<string name="owner_name" msgid="8713560351570795743">"মালিক"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"আনপিন করার আগে আনলক প্যাটার্ন চান"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"আনপিন করার আগে পাসওয়ার্ড চান"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"আপনার অ্যাডমিন ইনস্টল করেছেন।\nঅনুমোদন করা অনুমতি দেখতে সেটিংসে যান"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"আপনার প্রশাসক আপডেট করেছেন"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"আপনার প্রশাসক মুছে দিয়েছেন"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ঠিক আছে"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ব্যাটারি সেভার ডার্ক থিম চালু করে এবং ব্যাকগ্রাউন্ড অ্যাক্টিভিটি, কিছু ভিজ্যুয়াল এফেক্ট, নির্দিষ্ট ফিচার ও কয়েকটি নেটওয়ার্ক কানেকশনের ব্যবহার সীমিত করে বা বন্ধ করে দেয়।"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"স্ক্রল করুন"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"পজ করুন"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"পজিশন"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> সীমাবদ্ধ গ্রুপে অন্তর্ভুক্ত করা হয়েছে"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"একটি ছবি পাঠানো হয়েছে"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"৩য় অফিস"</string>
<string name="profile_label_test" msgid="9168641926186071947">"পরীক্ষা"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"কমিউনাল"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"অফিস প্রোফাইল"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"প্রাইভেট স্পেস"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ক্লোন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 9c553cedadc5..e5a78a34d5d6 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Način rada jednom rukom"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatno zatamnjenje"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušni aparati"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatski klik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nije povezano"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Možete koristiti mikrofon slušnog aparata za pozivanje bez dodira. Ovo mijenja mikrofon samo tokom poziva."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Promijeni"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Postavke"</string>
- <string name="user_switched" msgid="7249833311585228097">"Trenutni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na korisnika <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjava korisnika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži uzorak za otključavanje prije poništavanja kačenja"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži lozinku prije nego se otkači"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalirao je vaš administrator.\nIdite u postavke da pregledate data odobrenja"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao je vaš administrator"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao je vaš administrator"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Uredu"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ušteda baterije uključuje tamnu temu i ograničava ili isključuje aktivnost u pozadini, određene vizuelne efekte i funkcije te neke mrežne veze."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Klizanje"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pauziraj"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Položaj"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je stavljen u odjeljak OGRANIČENO"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslao/la sliku"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"3. poslovno"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Testno"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Opće"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Radni profil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatni prostor"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 3fa177ef2116..b70bb3337b7c 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode d\'una mà"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuació extra"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Audiòfons"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automàtic"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconnectat"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connectat"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Actiu"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Pots fer servir el micròfon del teu audiòfon per fer trucades amb mans lliures. Aquesta opció només canvia el micròfon durant la trucada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Canvia"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configuració"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuari actual: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"S\'està canviant a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"S\'està tancant la sessió de l\'usuari <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietari"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Sol·licita el patró de desbloqueig per deixar de fixar"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Demana la contrasenya per deixar de fixar"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instal·lat per l\'administrador.\nVes a la configuració per veure els permisos concedits."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualitzat per l\'administrador"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Suprimit per l\'administrador"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"D\'acord"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions de xarxa."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Desplaça"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Posa en pausa"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posició"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> s\'ha transferit al segment RESTRINGIT"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ha enviat una imatge"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Treball 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Prova"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Compartit"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de treball"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espai privat"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 670ca33f0e1d..c4e69ab37ef7 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jedné ruky"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Velmi tmavé zobrazení"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Naslouchátka"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatické kliknutí"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Odpojeno"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Připojeno"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivní"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"K handsfree telefonování můžete použít mikrofon naslouchátka. Mikrofon se přepne jen během hovoru."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Přepnout"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavení"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuální uživatel je <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Přepínání na uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odhlašování uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlastník"</string>
@@ -1969,7 +1967,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Před uvolněním požádat o bezpečnostní gesto"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Před odepnutím požádat o heslo"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Nainstalováno administrátorem.\nUdělená oprávnění si můžete prohlédnout v nastavení."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Aktualizováno administrátorem"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Smazáno administrátorem"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Spořič baterie zapíná tmavý motiv a omezuje či vypíná aktivitu na pozadí, některé vizuální efekty, některé funkce a připojení k některým sítím."</string>
@@ -2277,6 +2276,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Posunutí"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pozastavit"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozice"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Balíček <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> byl vložen do sekce OMEZENO"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"posílá obrázek"</string>
@@ -2484,6 +2495,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Práce 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Komunální"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Pracovní profil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Soukromý prostor"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 47eb357b4087..c0447239859d 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enhåndstilstand"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra dæmpet belysning"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Høreapparater"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoklik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ikke forbundet"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Forbundet"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kan bruge mikrofonen i dit høreapparat til at foretage håndfrie opkald. Der skiftes kun mikrofon under opkaldet."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Skift"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Indstillinger"</string>
- <string name="user_switched" msgid="7249833311585228097">"Nuværende bruger <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Skifter til <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> logges ud…"</string>
<string name="owner_name" msgid="8713560351570795743">"Ejer"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Bed om oplåsningsmønster ved deaktivering"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Bed om adgangskode inden frigørelse"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installeret af din administrator.\nGå til Indstillinger for at se de tilladelser, der er blevet givet"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Opdateret af din administrator"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Slettet af din administrator"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Batterisparefunktionen aktiverer Mørkt tema og begrænser eller deaktiverer aktivitet i baggrunden og visse visuelle effekter, funktioner og netværksforbindelser."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Rul"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Sæt på pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Placering"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> er blevet placeret i samlingen BEGRÆNSET"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sendte et billede"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Arbejde 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Fælles"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Arbejdsprofil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 06a35e628d27..0d371cceba6b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Einhandmodus"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extradunkel"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hörgeräte"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatischer Klick"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nicht verbunden"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Verbunden"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kannst das Mikrofon deines Hörgeräts für Anrufe per Sprachbefehl verwenden. Das Mikrofon wird nur für die Dauer des Anrufs gewechselt."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Wechseln"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Einstellungen"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktueller Nutzer <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Wechseln zu <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> wird abgemeldet…"</string>
<string name="owner_name" msgid="8713560351570795743">"Eigentümer"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Vor dem Beenden nach Entsperrungsmuster fragen"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vor dem Beenden nach Passwort fragen"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Von deinem Administrator installiert.\nRufe die Einstellungen auf, um gewährte Berechtigungen anzusehen."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Von deinem Administrator aktualisiert"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Von deinem Administrator gelöscht"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Der Energiesparmodus aktiviert das dunkle Design. Hintergrundaktivitäten, einige Funktionen und optische Effekte und manche Netzwerkverbindungen werden eingeschränkt oder deaktiviert."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scrollen"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausieren"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> wurde in den BESCHRÄNKT-Bucket gelegt"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"hat ein Bild gesendet"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Geschäftlich 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Gemeinsam genutzt"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Arbeitsprofil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Vertrauliches Profil"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 8a12e0e907b1..55d28c500b2d 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Λειτουργία ενός χεριού"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Επιπλέον μείωση φωτεινότητας"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Συσκευές ακοής"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Αυτόματο κλικ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Αποσυνδέθηκε"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Συνδέθηκε"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Ενεργή"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Μπορείτε να χρησιμοποιήσετε το μικρόφωνο του βοηθήματος ακοής σας για κλήσεις handsfree. Με αυτή την ενέργεια, το μικρόφωνό σας αλλάζει μόνο κατά τη διάρκεια της κλήσης."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Εναλλαγή"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ρυθμίσεις"</string>
- <string name="user_switched" msgid="7249833311585228097">"Τρέχων χρήστης <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Εναλλαγή σε <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Αποσύνδεση <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Κάτοχος"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Να γίνεται ερώτηση για το μοτίβο ξεκλειδώματος, πριν από το ξεκαρφίτσωμα"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Να γίνεται ερώτηση για τον κωδικό πρόσβασης, πριν από το ξεκαρφίτσωμα"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Εγκαταστάθηκε από τον διαχειριστή σας.\nΜεταβείτε στις ρυθμίσεις για να δείτε τις άδειες που έχουν εκχωρηθεί"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ενημερώθηκε από τον διαχειριστή σας"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Διαγράφηκε από τον διαχειριστή σας"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Η Εξοικονόμηση μπαταρίας ενεργοποιεί το Σκούρο θέμα και περιορίζει ή απενεργοποιεί τη δραστηριότητα στο παρασκήνιο, ορισμένα οπτικά εφέ, συγκεκριμένες λειτουργίες και κάποιες συνδέσεις δικτύου."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Κύλιση"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Παύση"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Θέση"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Το πακέτο <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> τοποθετήθηκε στον κάδο ΠΕΡΙΟΡΙΣΜΕΝΗΣ ΠΡΟΣΒΑΣΗΣ."</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"έστειλε μια εικόνα"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Εργασία 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Δοκιμή"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Κοινόχρηστο"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Προφίλ εργασίας"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ιδιωτικός χώρος"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Κλώνος"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 3881f1de0002..b94840430e60 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hearing devices"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnected"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connected"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Active"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to Settings to view granted permissions"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Updated by your admin.\nGo to settings to view granted permissions"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scroll"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index e6c4047cbda8..30843adab245 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1824,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
@@ -1966,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to settings to view granted permissions"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Updated by your admin.\nGo to settings to view granted permissions"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
@@ -2274,6 +2273,12 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scroll"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <string name="accessibility_autoclick_scroll_up" msgid="2044948780797117443">"Scroll Up"</string>
+ <string name="accessibility_autoclick_scroll_down" msgid="3733401063292018116">"Scroll Down"</string>
+ <string name="accessibility_autoclick_scroll_left" msgid="8564421367992824198">"Scroll Left"</string>
+ <string name="accessibility_autoclick_scroll_right" msgid="8932417330753984265">"Scroll Right"</string>
+ <string name="accessibility_autoclick_scroll_exit" msgid="3788610039146769696">"Exit Scroll Mode"</string>
+ <string name="accessibility_autoclick_scroll_panel_title" msgid="7120598166296447036">"Scroll Panel"</string>
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
@@ -2481,6 +2486,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 9680b83af481..bcfc861932fb 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hearing devices"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnected"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connected"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Active"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to Settings to view granted permissions"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Updated by your admin.\nGo to settings to view granted permissions"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scroll"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index fbe1c0a09a1c..15ea9bf0007c 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hearing devices"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnected"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connected"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Active"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to Settings to view granted permissions"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Updated by your admin.\nGo to settings to view granted permissions"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scroll"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 54297a568091..1e10140e2bf1 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo de una mano"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dispositivos auditivos"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automático"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Activo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puedes usar el micrófono de tu audífono para realizar llamadas sin usar las manos. Esto solo cambia el micrófono durante la llamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambiar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configuración"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuario actual: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Cambiando a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Saliendo de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietario"</string>
@@ -1968,7 +1966,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicitar desbloqueo para quitar fijación"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicitar contraseña para quitar fijación"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Tu administrador realizó la instalación.\nVe a la configuración para ver los permisos otorgados"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Tu administrador actualizó este paquete"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Tu administrador realizó la actualización.\nVe a la configuración para ver los permisos otorgados"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Tu administrador borró este paquete"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Aceptar"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"El Ahorro de batería activa el Tema oscuro y desactiva o restringe la actividad en segundo plano, algunos efectos visuales, algunas conexiones de red y otras funciones determinadas."</string>
@@ -2276,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Desplazamiento"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausar"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posición"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Se colocó <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> en el bucket RESTRICTED"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"envió una imagen"</string>
@@ -2483,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Trabajo 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Probar"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Compartido"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabajo"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espacio privado"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 5bd2bf5941c3..189a027350bb 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo Una mano"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Audífonos"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automático"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Activo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puedes usar el micrófono de tu audífono para hacer llamadas en manos libres. Esta opción solo cambia el micrófono durante la llamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambiar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ajustes"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuario actual: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Cambiando a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Cerrando la sesión de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietario"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir patrón de desbloqueo para dejar de fijar"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicitar contraseña para desactivar"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado por tu administrador.\nVe a Ajustes para ver los permisos concedidos."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizado por el administrador"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminado por el administrador"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Aceptar"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ahorro de batería activa el tema oscuro y limita o desactiva la actividad en segundo plano, algunos efectos visuales, ciertas funciones y algunas conexiones de red."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Desplazarse"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausar"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posición"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> se ha incluido en el grupo de restringidos"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ha enviado una imagen"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Trabajo 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Prueba"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Común"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabajo"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espacio privado"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 0c928a6c601d..893bc4e755fa 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -652,7 +652,7 @@
<string name="permdesc_imagesWrite" msgid="5195054463269193317">"Võimaldab rakendusel muuta teie fotokogu."</string>
<string name="permlab_mediaLocation" msgid="7368098373378598066">"Lugeda teie meediakogus olevaid asukohti"</string>
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Võimaldab rakendusel lugeda teie meediakogus olevaid asukohti."</string>
- <string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"ligikaudse pilgu jälgimine"</string>
+ <string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"pilgu ligikaudne jälgimine"</string>
<string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Võimaldab rakendusel teie pilku ligikaudselt jälgida."</string>
<string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"pilgu suuna jälgimine"</string>
<string name="permdesc_eye_tracking_fine" msgid="5788889152304524730">"Võimaldab rakendusel juurde pääseda täpsetele pilguandmetele."</string>
@@ -665,9 +665,9 @@
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"vahetu keskkonna mõistmine"</string>
<string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Võimaldab rakendusel juurde pääseda teie vahetu keskkonna jälgimisandmetele."</string>
<string name="permlab_scene_understanding_fine" msgid="409126403264393251">"vahetu keskkonna väga üksikasjalik mõistmine"</string>
- <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Võimaldab rakendusel pääseda väga üksikasjalikult juurde teie vahetu keskkonna jälgimisandmetele."</string>
- <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"juurdepääs XR-i andmetele, kui see pole esiplaanil"</string>
- <string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Võimaldab rakendusel pääseda juurde XR-i andmetele, kui see pole esiplaanil."</string>
+ <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Võimaldab rakendusel pääseda juurde teie ümbruskonna üksikasjalikele jälgimisandmetele."</string>
+ <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"juurdepääs XR-i andmetele, kui rakendus pole esiplaanil"</string>
+ <string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Võimaldab rakendusel pääseda juurde XR-i andmetele, kui rakendus pole esiplaanil."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"Biomeetria kasutamine"</string>
<string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biomeetria või ekraaniluku kasutamine"</string>
<string name="biometric_dialog_default_title" msgid="55026799173208210">"Kinnitage oma isik"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Ühekäerežiim"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Eriti tume"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Kuuldeseadmed"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automaatklikk"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ühendus katkestatud"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Ühendatud"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiivne"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Saate kasutada oma kuuldeaparaadi mikrofon vabakäerežiimis helistamiseks. See vahetab teie mikrofoni ainult kõne ajal."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Vaheta"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Seaded"</string>
- <string name="user_switched" msgid="7249833311585228097">"Praegune kasutaja <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Üleminek kasutajale <xliff:g id="NAME">%1$s</xliff:g> ..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Kasutaja <xliff:g id="NAME">%1$s</xliff:g> väljalogimine …"</string>
<string name="owner_name" msgid="8713560351570795743">"Omanik"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Enne vabastamist küsi avamismustrit"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Enne vabastamist küsi parooli"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installis teie administraator.\nAntud õiguste vaatamiseks avage seaded"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Administraator on seda värskendanud"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Administraator on selle kustutanud"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Akusäästja lülitab sisse tumeda teema ja lülitab välja taustategevused, mõned visuaalsed efektid, teatud funktsioonid ja võrguühendused või piirab neid."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Keri"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Peata"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Asukoht"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> on lisatud salve PIIRANGUTEGA"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"saatis kujutise"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Töö 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Jagatud"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Tööprofiil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privaatne ruum"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 13f0452b2f3a..fb60c8dc6e8d 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Esku bakarreko modua"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Are ilunago"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Entzumen-gailuak"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatikoki klik egiteko eginbidea"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Deskonektatuta"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Konektatuta"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktibo"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Audifonoaren mikrofonoa erabil dezakezu esku libreko deiak egiteko. Deirako soilik aldatzen da mikrofonoa."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Aldatu"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ezarpenak"</string>
- <string name="user_switched" msgid="7249833311585228097">"Erabiltzailea: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"\"<xliff:g id="NAME">%1$s</xliff:g>\" erabiltzailera aldatzen…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> erabiltzailearen saioa amaitzen…"</string>
<string name="owner_name" msgid="8713560351570795743">"Jabea"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Eskatu desblokeatzeko eredua aingura kendu aurretik"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Eskatu pasahitza aingura kendu aurretik"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Administratzaileak instalatu du.\nEmandako baimenak ikusteko, joan ezarpenetara."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Administratzaileak eguneratu du"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Administratzaileak ezabatu du"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Ados"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Bateria-aurreztaileak gai iluna aktibatzen du, eta atzeko planoko jarduerak, zenbait efektu bisual, eta eginbide jakin eta sareko konexio batzuk mugatzen edo desaktibatzen ditu."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Egin gora eta behera"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausatu"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Ezarri posizioan"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Murriztuen edukiontzian ezarri da <xliff:g id="PACKAGE_NAME">%1$s</xliff:g>"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"erabiltzaileak irudi bat bidali du"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Lanekoa 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Probakoa"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Partekatua"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Laneko profila"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Eremu pribatua"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klona"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 2eaf4c033d52..24db15aa7b26 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"حالت یک‌دستی"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"بسیار کم‌نور"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"دستگاه‌های کمک‌شنوایی"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"کلیک خودکار"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"متصل نیست"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"وصل شد"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"فعال"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"برای تماس دست‌آزاد می‌توانید از میکروفون سمعک خود استفاده کنید. این کار فقط درطول تماس میکروفون شما را عوض می‌کند."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"عوض کردن"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"تنظیمات"</string>
- <string name="user_switched" msgid="7249833311585228097">"کاربر کنونی <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"در حالت تغییر به <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"در حال خروج از سیستم <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"مالک"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"درخواست الگوی بازگشایی قفل قبل‌از برداشتن سنجاق"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"درخواست گذرواژه قبل از برداشتن سنجاق"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"سرپرست شما آن را نصب کرده است.\nبرای مشاهده اجازه‌های اعطاشده به تنظیمات بروید"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"توسط سرپرست سیستم به‌روزرسانی شد"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"سرپرستتان آن را به‌روز کرده است.\nبرای مشاهده اجازه‌های اعطاشده به تنظیمات بروید"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"توسط سرپرست سیستم حذف شد"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"تأیید"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"«بهینه‌سازی باتری» «زمینه تاریک» را روشن می‌کند و فعالیت پس‌زمینه، برخی از جلوه‌های بصری، ویژگی‌هایی خاص، و برخی از اتصال‌های شبکه را محدود یا خاموش می‌کند."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"پیمایش"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"توقف موقت"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"موقعیت"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> در سطل «محدودشده» قرار گرفت"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"تصویری ارسال کرد"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"کار ۳"</string>
<string name="profile_label_test" msgid="9168641926186071947">"آزمایش"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"عمومی"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"نمایه کاری"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"فضای خصوصی"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"همسانه‌سازی"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 280658c26735..cb16427bd351 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Yhden käden moodi"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Erittäin himmeä"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Kuulolaitteet"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automaattinen klikkaus"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Yhteys katkaistu"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Yhdistetty"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiivinen"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Voit soittaa ääniohjatusti kuulolaitteen mikrofonin avulla. Tämä vaihtaa mikrofonia vain puhelun ajaksi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Vaihda"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Asetukset"</string>
- <string name="user_switched" msgid="7249833311585228097">"Nykyinen käyttäjä: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Vaihdetaan käyttäjään <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> kirjautuu ulos…"</string>
<string name="owner_name" msgid="8713560351570795743">"Omistaja"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pyydä lukituksenpoistokuvio ennen irrotusta"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pyydä salasana ennen irrotusta"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Järjestelmänvalvojan asentama.\nTarkista myönnetyt luvat asetuksista."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Järjestelmänvalvoja päivitti tämän."</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Järjestelmänvalvoja poisti tämän."</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Virransäästö laittaa tumman teeman päälle ja rajoittaa tai laittaa pois päältä taustatoimintoja, tiettyjä ominaisuuksia sekä joitakin visuaalisia tehosteita ja verkkoyhteyksiä."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Vieritä"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Keskeytä"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Sijainti"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> on nyt rajoitettujen ryhmässä"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"lähetti kuvan"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Työ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Testi"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Jaettu"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Työprofiili"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Yksityinen tila"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klooni"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 1740ce536ea5..c2b368e7c02c 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode Une main"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Très sombre"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Appareils auditifs"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automatique"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Déconnecté"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connecté"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Actif"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Vous pouvez utiliser votre microphone pour prothèse auditive pour faire des appels en mode mains libres. Cette option ne change votre micro que pendant l\'appel."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Changer"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Paramètres"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Passage au profil : <xliff:g id="NAME">%1$s</xliff:g> en cours…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Déconnexion de <xliff:g id="NAME">%1$s</xliff:g> en cours..."</string>
<string name="owner_name" msgid="8713560351570795743">"Propriétaire"</string>
@@ -1968,7 +1966,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Demander le schéma de déverrouillage avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Demander le mot de passe avant d\'annuler l\'épinglage"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installé par votre administrateur.\nAccédez aux paramètres pour consulter les autorisations accordées"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Mise à jour par votre administrateur"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Installé par votre administrateur.\nAccédez aux paramètres pour consulter les autorisations accordées"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Supprimé par votre administrateur"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Le mode Économiseur de pile active le thème sombre et limite ou désactive l\'activité en arrière-plan, certains effets visuels, certaines fonctionnalités et certaines connexions réseau."</string>
@@ -2276,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Faire défiler"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> a été placé dans le compartiment RESTREINT"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g> :"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"a envoyé une image"</string>
@@ -2483,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Professionnel 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Commun"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil professionnel"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espace privé"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0564661de80d..1cf540905515 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode une main"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Luminosité ultra-réduite"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Appareils auditifs"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automatique"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Déconnecté"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connecté"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Actif"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Vous pouvez utiliser le micro de votre appareil auditif pour passer des appels en mode mains-libres. Cette option change uniquement le micro pendant l\'appel."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Changer"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Paramètres"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Passage à <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Déconnexion de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propriétaire"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Demander le schéma de déverrouillage avant de retirer l\'épingle"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Demander le mot de passe avant de retirer l\'épingle"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installé par votre administrateur.\nAllez dans les paramètres pour consulter les autorisations accordées."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Mis à jour par votre administrateur"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Supprimé par votre administrateur"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"L\'économiseur de batterie active le thème sombre et limite ou désactive l\'activité en arrière-plan ainsi que certains effets visuels, fonctionnalités et connexions réseau."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Faire défiler"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> a été placé dans le bucket RESTRICTED"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g> :"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"a envoyé une image"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Professionnel 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Commun"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil professionnel"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espace privé"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 73bed7fa72da..ecd2d4b197d2 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo dunha soa man"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dispositivos auditivos"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automático"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Activo"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Podes usar o micrófono do audiófono para facer chamadas coas mans libres. Esta acción só cambia o micrófono durante a chamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambiar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configuración"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuario actual <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Cambiando a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Pechando sesión de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietario"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrón de desbloqueo antes de soltar a fixación"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir contrasinal antes de soltar a fixación"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pola persoa administradora.\nVai á configuración para ver os permisos concedidos"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizado polo teu administrador"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminado polo teu administrador"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Aceptar"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Coa función Aforro de batería, actívase o tema escuro e restrínxense ou desactívanse a actividade en segundo plano, algúns efectos visuais e determinadas funcións e conexións de rede."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Desprazar"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausa"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posición"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> incluíuse no grupo RESTRINXIDO"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviouse unha imaxe"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Traballo 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Proba"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Compartido"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de traballo"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espazo privado"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clonado"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 0c99096ff4fc..b4095ef8dbd3 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"તમે હાથના ઉપયોગ વિના કૉલ કરવા માટે સાંભળવામાં મદદ આપતા તમારા યંત્રના માઇક્રોફોનનો ઉપયોગ કરી શકો છો. કૉલ દરમિયાન આ ફક્ત તમારા માઇકને સ્વિચ કરે છે."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"સ્વિચ કરો"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"સેટિંગ"</string>
- <string name="user_switched" msgid="7249833311585228097">"વર્તમાન વપરાશકર્તા <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> પર સ્વિચ કરી રહ્યાં છીએ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> લોગ આઉટ થઈ રહ્યાં છે…"</string>
<string name="owner_name" msgid="8713560351570795743">"માલિક"</string>
@@ -1967,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"અનપિન કરતા પહેલાં અનલૉક પૅટર્ન માટે પૂછો"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"અનપિન કરતાં પહેલાં પાસવર્ડ માટે પૂછો"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"તમારા ઍડમિન દ્વારા ઇન્સ્ટૉલ કરવામાં આવ્યું છે.\nઆપેલી પરવાનગીઓ જોવા માટે સેટિંગ પર જાઓ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"તમારા વ્યવસ્થાપક દ્વારા અપડેટ કરવામાં આવેલ છે"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"તમારા વ્યવસ્થાપક દ્વારા કાઢી નાખવામાં આવેલ છે"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ઓકે"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"બૅટરી સેવર ઘેરી થીમની સુવિધા ચાલુ કરે છે અને બૅકગ્રાઉન્ડ પ્રવૃત્તિ, અમુક વિઝ્યુઅલ ઇફેક્ટ, અમુક સુવિધાઓ અને કેટલાક નેટવર્ક કનેક્શન મર્યાદિત કે બંધ કરે છે."</string>
@@ -2275,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"સ્ક્રોલ કરો"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"થોભાવો"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"સ્થિતિ"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>ને પ્રતિબંધિત સમૂહમાં મૂકવામાં આવ્યું છે"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"છબી મોકલી"</string>
@@ -2482,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ઑફિસ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"પરીક્ષણ કરો"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"કૉમ્યુનલ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ઑફિસની પ્રોફાઇલ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ખાનગી સ્પેસ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ક્લોન"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index df211dc458a7..f93b8a2ec269 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"वन-हैंडेड मोड"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"कान की मशीन"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"अपने-आप क्लिक होने की सुविधा"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"डिसकनेक्ट हो गया"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"कनेक्ट हो गया"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"चालू है"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"बोलकर कॉल का जवाब देने के लिए, \'कान की मशीन का माइक्रोफ़ोन\' इस्तेमाल किया जा सकता है. इससे, कॉल के दौरान सिर्फ़ आपका माइक चालू या बंद होता है."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"स्विच करें"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"सेटिंग"</string>
- <string name="user_switched" msgid="7249833311585228097">"मौजूदा उपयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> पर स्विच किया जा रहा है…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> द्वारा प्रस्‍थान किया जा रहा है…"</string>
<string name="owner_name" msgid="8713560351570795743">"मालिक"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"अनपिन करने से पहले लॉक खोलने के पैटर्न के लिए पूछें"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"अनपिन करने से पहले पासवर्ड के लिए पूछें"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"इसे आपके एडमिन ने इंस्टॉल किया है.\nजिन अनुमतियों को मंज़ूरी मिली है उन्हें देखने के लिए, सेटिंग में जाएं"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"आपके व्यवस्थापक ने अपडेट किया है"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"आपके व्यवस्थापक ने हटा दिया है"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ठीक है"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"बैटरी सेवर, गहरे रंग वाली थीम को चालू करता है. साथ ही, इस मोड में बैकग्राउंड की गतिविधि, कुछ विज़ुअल इफ़ेक्ट, और कुछ खास सुविधाएं कम या बंद हो जाती हैं. कुछ इंटरनेट कनेक्शन भी पूरी तरह काम नहीं करते."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"स्क्रोल करें"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"रोकें"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"पोज़िशन"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> को प्रतिबंधित बकेट में रखा गया है"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"एक इमेज भेजी गई"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ऑफ़िस 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"टेस्ट"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"कम्यूनिटी"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"वर्क प्रोफ़ाइल"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"प्राइवेट स्पेस"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index f701483453aa..336e37956783 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -354,9 +354,9 @@
<string name="permgroupdesc_sensors" msgid="2610631290633747752">"pristupiti podacima senzora o vašim vitalnim znakovima"</string>
<string name="permgrouplab_notifications" msgid="5472972361980668884">"Obavijesti"</string>
<string name="permgroupdesc_notifications" msgid="4608679556801506580">"prikazati obavijesti"</string>
- <string name="permgrouplab_xr_tracking" msgid="7418994009794287471">"XR podaci o praćenju"</string>
- <string name="permgroupdesc_xr_tracking" msgid="6777198859446500821">"pristup XR podacima o vama i vašem okruženju"</string>
- <string name="permgrouplab_xr_tracking_sensitive" msgid="1194833982988144536">"osjetljivi XR podaci o praćenju"</string>
+ <string name="permgrouplab_xr_tracking" msgid="7418994009794287471">"podaci o praćenju za proširenu stvarnost"</string>
+ <string name="permgroupdesc_xr_tracking" msgid="6777198859446500821">"pristup podacima proširene stvarnosti o vama i vašem okruženju"</string>
+ <string name="permgrouplab_xr_tracking_sensitive" msgid="1194833982988144536">"osjetljivi podaci o praćenju za XR"</string>
<string name="permgroupdesc_xr_tracking_sensitive" msgid="9178027369004805829">"pristup osjetljivim podacima o praćenju, kao što je pogled"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Dohvaćati sadržaj prozora"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Pregledat će sadržaj prozora koji upotrebljavate."</string>
@@ -655,7 +655,7 @@
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Omogućuje aplikaciji čitanje lokacija iz vaše medijske zbirke."</string>
<string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"praćenje približnog pogleda"</string>
<string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Aplikaciji omogućuje praćenje približnih podataka o pogledu."</string>
- <string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"praćenje toga gdje gledate"</string>
+ <string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"praćenje mjesta u koje gledate"</string>
<string name="permdesc_eye_tracking_fine" msgid="5788889152304524730">"Aplikaciji omogućuje pristup preciznim podacima o pogledu."</string>
<string name="permlab_face_tracking" msgid="2272048395128283324">"praćenje lica"</string>
<string name="permdesc_face_tracking" msgid="2622783922311211866">"Aplikaciji omogućuje pristup podacima o praćenju lica."</string>
@@ -664,10 +664,10 @@
<string name="permlab_head_tracking" msgid="1309731456372087270">"praćenje pokreta glave"</string>
<string name="permdesc_head_tracking" msgid="231597390513699188">"Aplikaciji omogućuje pristup podacima o praćenju pokreta glave."</string>
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"stjecanje uvida u neposredno okruženje"</string>
- <string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Aplikaciji omogućuje pristup podacima o praćenju o vašem neposrednom okruženju."</string>
+ <string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Aplikaciji omogućuje pristup podacima o praćenju vašeg neposrednog okruženja."</string>
<string name="permlab_scene_understanding_fine" msgid="409126403264393251">"stjecanje detaljnih uvida u neposredno okruženje"</string>
- <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Aplikaciji omogućuje pristup detaljnim podacima o praćenju o vašem neposrednom okruženju."</string>
- <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"pristup XR podacima dok nije u prednjem planu"</string>
+ <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Aplikaciji omogućuje pristup detaljnim podacima o praćenju vašeg neposrednog okruženja."</string>
+ <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"pristup podacima proširene stvarnosti dok nije u prednjem planu"</string>
<string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Aplikaciji omogućuje pristup XR podacima dok nije u prednjem planu."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"Upotreba biometrije"</string>
<string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Upotreba biometrijske autentifikacije ili zaključavanja zaslona"</string>
@@ -1714,7 +1714,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Bežični prikaz"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Emitiranje"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Povezivanje s uređajem"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Emitiranje zaslona na uređaj"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Emitirajte zaslon na uređaj"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"Traženje uređaja…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Postavke"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Prekini vezu"</string>
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Način rada jednom rukom"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Još tamnije"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušna pomagala"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatski klik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nije povezano"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mikrofon slušnog pomagala možete koristiti za pozivanje bez upotrebe ruku. Time se mikrofon prebacuje samo tijekom poziva."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Prebaci"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Postavke"</string>
- <string name="user_switched" msgid="7249833311585228097">"Trenutačni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na korisnika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljivanje korisnika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži uzorak za otključavanje radi otkvačivanja"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži zaporku radi otkvačivanja"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalirao vaš administrator.\nOtvorite postavke da biste pregledali dodijeljena dopuštenja"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao administrator"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao administrator"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"U redu"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Štednja baterije uključuje tamnu temu i ograničava ili isključuje aktivnosti u pozadini, neke vizualne efekte, određene značajke i neke mrežne veze."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Pomakni se"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pauziraj"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozicija"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> premješten je u spremnik OGRANIČENO"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"šalje sliku"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Posao 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Zajedničko"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Radni profil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatni prostor"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index e3d261e11da8..1519326cdb6b 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Egykezes mód"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extrasötét"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hallásjavító eszközök"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatikus kattintás"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Leválasztva"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Csatlakozva"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktív"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Használhatja hallókészüléke mikrofonját a szabadkezes hívásokhoz. Csak a mikrofont kapcsolja át hívás közben."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Váltás"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Beállítások"</string>
- <string name="user_switched" msgid="7249833311585228097">"<xliff:g id="NAME">%1$s</xliff:g> az aktuális felhasználó."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Átváltás erre: <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> kijelentkeztetése folyamatban van…"</string>
<string name="owner_name" msgid="8713560351570795743">"Tulajdonos"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Feloldási minta kérése a kitűzés feloldásához"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Jelszó kérése a rögzítés feloldásához"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"A rendszergazda által telepítve.\nLépjen a beállításokhoz a megadott engedélyek megtekintéséhez."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"A rendszergazda által frissítve"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"A rendszergazda által frissítve.\nLépjen a beállításokhoz a megadott engedélyek megtekintéséhez."</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"A rendszergazda által törölve"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Az Akkumulátorkímélő mód bekapcsolja a Sötét témát, és korlátozza vagy kikapcsolja a háttérbeli tevékenységeket, valamint bizonyos vizuális effekteket, funkciókat és hálózati kapcsolatokat."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Görgetés"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Szüneteltetés"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozíció"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"A következő csomag a KORLÁTOZOTT csoportba került: <xliff:g id="PACKAGE_NAME">%1$s</xliff:g>"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"képet küldött"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"3. munkahelyi"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Teszt"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Közös"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Munkaprofil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privát terület"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klón"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index de62af5376c7..af9ee628e4b1 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Մեկ ձեռքի ռեժիմ"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Հավելյալ խամրեցում"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Լսողական սարքեր"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Ավտոմատ սեղմում"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Անջատված է"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Միացված է"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Ակտիվ է"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Լսողական սարքի խոսափողը կարող եք օգտագործել ձայնային կառավարման համար։ Դուք կանցնեք խոսափողին միայն զանգի ընթացքում։"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Անցնել"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Կարգավորումներ"</string>
- <string name="user_switched" msgid="7249833311585228097">"Ներկայիս օգտատերը <xliff:g id="NAME">%1$s</xliff:g>:"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Անցում <xliff:g id="NAME">%1$s</xliff:g> պրոֆիլին..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Ելք <xliff:g id="NAME">%1$s</xliff:g>-ից…"</string>
<string name="owner_name" msgid="8713560351570795743">"Սեփականատեր"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Հարցնել ապակողպող նախշը"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Հարցնել գաղտնաբառը"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Տեղադրվել է ադմինիստրատորի կողմից։\nԱնցեք կարգավորումներ՝ տրամադրված թույլտվությունները դիտելու համար"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Թարմացվել է ձեր ադմինիստրատորի կողմից"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Թարմացվել է ձեր ադմինիստրատորի կողմից։\nԱնցեք կարգավորումներ՝ տրամադրված թույլտվությունները դիտելու համար"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Ջնջվել է ձեր ադմինիստրատորի կողմից"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Եղավ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"«Մարտկոցի տնտեսում» գործառույթը միացնում է մուգ թեման և անջատում կամ սահմանափակում է աշխատանքը ֆոնային ռեժիմում, որոշ վիզուալ էֆեկտներ, ցանցային միացումներ և այլ գործառույթներ։"</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Ոլորել"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Դադարեցնել"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Դիրքը"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> փաթեթը գցվեց ՍԱՀՄԱՆԱՓԱԿՎԱԾ զամբյուղի մեջ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>՝"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"օգտատերը պատկեր է ուղարկել"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Աշխատանքային 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Փորձնական"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Ընդհանուր"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Աշխատանքային պրոֆիլ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Մասնավոր տարածք"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Կլոն"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 18ae5e9715f9..90b1ddfcc2cb 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode satu tangan"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra redup"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Alat bantu dengar"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Klik otomatis"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Tidak terhubung"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Terhubung"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktif"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Anda dapat menggunakan mikrofon alat bantu dengar untuk melakukan panggilan handsfree. Tindakan ini hanya akan mengalihkan mikrofon Anda selama panggilan."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Alihkan"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Setelan"</string>
- <string name="user_switched" msgid="7249833311585228097">"Pengguna saat ini <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Beralih ke <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Mengeluarkan <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Pemilik"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Meminta pola pembukaan kunci sebelum melepas sematan"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Meminta sandi sebelum melepas sematan"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Diinstal oleh admin Anda.\nBuka setelan untuk melihat izin yang diberikan"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Diupdate oleh admin Anda"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Dihapus oleh admin Anda"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Oke"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Penghemat Baterai akan mengaktifkan Tema gelap dan membatasi atau menonaktifkan aktivitas latar belakang, beberapa efek visual, fitur tertentu, dan beberapa koneksi jaringan."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scroll"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Jeda"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posisi"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> telah dimasukkan ke dalam bucket DIBATASI"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"mengirim gambar"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Kerja 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Pengujian"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang privasi"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index a74ff1cadcb7..772be761426d 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1713,7 +1713,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Þráðlaus skjábirting"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Senda út"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Tengjast tæki"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Senda skjá út í tæki"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Varpa skjá í tæki"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"Leitar að tækjum…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Stillingar"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Aftengja"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Einhent stilling"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Mjög dökkt"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Heyrnartæki"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Sjálfvirkur smellur"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Aftengt"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Tengt"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Virkt"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Þú getur notað hljóðnema heyrnartækisins þíns til að hringja eða svara símtölum handfrjálst. Aðeins verður skipt um hljóðnema á meðan á símtali stendur."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Skipta"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Stillingar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Núverandi notandi <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Skiptir yfir á <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Skráir <xliff:g id="NAME">%1$s</xliff:g> út…"</string>
<string name="owner_name" msgid="8713560351570795743">"Eigandi"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Biðja um opnunarmynstur til að losa"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Biðja um aðgangsorð til að losa"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Sett upp af stjórnanda.\nFarðu í stillingar til að sjá heimildir"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Kerfisstjóri uppfærði"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Uppfært af stjórnanda.\nFarðu í stillingarnar þínar til að sjá þær heimildir sem hafa verið veittar"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Kerfisstjóri eyddi"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Í lagi"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Rafhlöðusparnaður kveikir á dökku þema og takmarkar eða slekkur á bakgrunnsvirkni, sumum myndáhrifum, tilteknum eiginleikum og sumum nettengingum."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Fletta"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Hlé"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Staðsetning"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> var sett í flokkinn TAKMARKAÐ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sendi mynd"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Vinna 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Prófun"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Sameiginlegt"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Vinnusnið"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Leynirými"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Afrit"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index befd2b21776c..dd10d1d3688d 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -277,7 +277,7 @@
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Report completo"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Utilizza questa opzione per ridurre al minimo l\'interferenza di sistema quando il dispositivo non risponde, è troppo lento oppure quando ti servono tutte le sezioni della segnalazione. Non puoi inserire altri dettagli o acquisire altri screenshot."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Lo screenshot per la segnalazione di bug verrà acquisito tra # secondo.}many{Lo screenshot per la segnalazione di bug verrà acquisito tra # secondi.}other{Lo screenshot per la segnalazione di bug verrà acquisito tra # secondi.}}"</string>
- <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Screenshot con segnalazione di bug effettuato correttamente"</string>
+ <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Screenshot con segnalazione di bug effettuato"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Impossibile acquisire screenshot con segnalazione di bug"</string>
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modalità silenziosa"</string>
<string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Audio non attivo"</string>
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modalità a una mano"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Attenuazione extra"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Protesi uditive"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automatico"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnesso"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connesso"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Attivo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puoi usare il microfono dell\'apparecchio acustico per le chiamate in vivavoce. In questo modo, il microfono viene attivato solo durante la chiamata."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambia"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Impostazioni"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utente corrente <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Passaggio a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Disconnessione di <xliff:g id="NAME">%1$s</xliff:g> in corso…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietario"</string>
@@ -1968,7 +1966,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Richiedi sequenza di sblocco prima di sbloccare"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Richiedi password prima di sbloccare"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni concesse"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Aggiornato dall\'amministratore"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Aggiornato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni concesse"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminato dall\'amministratore"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Ok"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Il risparmio energetico attiva il tema scuro e limita o disattiva l\'attività in background, nonché alcuni effetti visivi, funzionalità e connessioni di rete."</string>
@@ -2276,13 +2274,25 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scorri"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Metti in pausa"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posizione"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> è stato inserito nel bucket RESTRICTED"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ha inviato un\'immagine"</string>
<string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Conversazione"</string>
<string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Conversazione di gruppo"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
- <string name="resolver_personal_tab" msgid="2051260504014442073">"Personale"</string>
+ <string name="resolver_personal_tab" msgid="2051260504014442073">"Personali"</string>
<string name="resolver_work_tab" msgid="2690019516263167035">"Lavoro"</string>
<string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Visualizzazione personale"</string>
<string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Visualizzazione di lavoro"</string>
@@ -2483,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Lavoro 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Condiviso"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profilo di lavoro"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Spazio privato"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 0b6c80af7dcb..1055612ff636 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"מצב שימוש ביד אחת"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"מעומעם במיוחד"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"מכשירי שמיעה"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"קליק אוטומטי"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"מנותק"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"מחובר"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"מצב פעיל"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"אפשר לשוחח במצב דיבורית באמצעות המיקרופון של מכשיר השמיעה. במצב הזה המיקרופון מוחלף רק במהלך השיחה."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"החלפה"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"הגדרות"</string>
- <string name="user_switched" msgid="7249833311585228097">"המשתמש הנוכחי <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"מעבר אל <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"מתבצע ניתוק של <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"בעלים"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"צריך לבקש קו ביטול נעילה לפני ביטול הצמדה"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"יש לבקש סיסמה לפני ביטול הצמדה"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"החבילה הותקנה על ידי האדמין.\nצריך לעבור להגדרות כדי לראות את ההרשאות שניתנו"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"עודכנה על ידי מנהל המערכת"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"נמחקה על ידי מנהל המערכת"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"אישור"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"התכונה \'חיסכון בסוללה\' מפעילה עיצוב כהה ומגבילה או מכבה פעילות ברקע, חלק מהאפקטים החזותיים, תכונות מסוימות וחלק מהחיבורים לרשתות."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"גלילה"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"השהיה"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"מיקום"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> התווספה לקטגוריה \'מוגבל\'"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"נשלחה תמונה"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"פרופיל עבודה 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"בדיקה"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"שיתופי"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"פרופיל העבודה"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"המרחב הפרטי"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"שכפול"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 2f8133a135a1..ea101ec8ee84 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"片手モード"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"さらに輝度を下げる"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"補聴器"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"自動クリック"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"未接続"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"接続済み"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"有効"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"補聴器のマイクを使ってハンズフリー通話を行えます。通話時のみマイクが切り替わります。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切り替える"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"設定"</string>
- <string name="user_switched" msgid="7249833311585228097">"現在のユーザーは<xliff:g id="NAME">%1$s</xliff:g>です。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>に切り替えています…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> をログアウトしています…"</string>
<string name="owner_name" msgid="8713560351570795743">"所有者"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"画面固定を解除する前にロック解除パターンの入力を求める"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"オフライン再生を解除する前にパスワードの入力を求める"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"管理者によりインストールされています。\n付与された権限を確認するには、設定に移動してください"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"管理者により更新されています"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"管理者により更新されています。\n付与された権限を確認するには、設定に移動してください"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"管理者により削除されています"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"バッテリー セーバーを ON にすると、ダークモードが ON になります。また、バックグラウンド アクティビティ、一部の視覚効果、特定の機能、一部のネットワーク接続が制限されるか OFF になります。"</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"スクロール"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"一時停止"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"位置"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> は RESTRICTED バケットに移動しました。"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"画像を送信しました"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"仕事用 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"テスト"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"共用"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"仕事用プロファイル"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"プライベート スペース"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ef154052843d..055c41201ced 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ცალი ხელის რეჟიმი"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"დამატებითი დაბინდვა"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"სმენის აპარატები"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ავტოდაწკაპუნება"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"კავშირი გაწყვეტილია"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"დაკავშირებული"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"აქტიური"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"შეგიძლიათ სმენის მოწყობილობის მიკროფონის გამოყენება უკონტაქტოდ დასარეკად. ეს გადართავს თქვენს მიკროფონს მხოლოდ ზარის დროს."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"გადართვა"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"პარამეტრები"</string>
- <string name="user_switched" msgid="7249833311585228097">"ამჟამინდელი მომხმარებელი <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>-ზე გადართვა…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-ის ანგარიშიდან გასვლა…"</string>
<string name="owner_name" msgid="8713560351570795743">"მფლობელი"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ფიქსაციის მოხსნამდე განბლოკვის ნიმუშის მოთხოვნა"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ფიქსაციის მოხსნამდე პაროლის მოთხოვნა"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"დაინსტალირებულია თქვენი ადმინისტრატორის მიერ.\nდაშვებული ნებართვების სანახავად გადადით პარამეტრებზე"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"განახლებულია თქვენი ადმინისტრატორის მიერ"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"განახლებულია თქვენი ადმინისტრატორის მიერ.\nდაშვებული ნებართვების სანახავად გადადით პარამეტრებზე"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"წაიშალა თქვენი ადმინისტრატორის მიერ"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"კარგი"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ბატარეის დამზოგი ჩართავს მუქ თემას და შეზღუდავს ან გამორთავს ფონურ აქტივობას, ზოგიერთ ვიზუალურ ეფექტს, გარკვეულ ფუნქციებსა და ზოგიერთ ქსელთან კავშირს."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"გადაადგილება"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"პაუზა"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"პოზიცია"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> მოთავსდა კალათაში „შეზღუდული“"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"გაიგზავნა სურათი"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"სამსახური 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"სატესტო"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"საერთო"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"სამსახურის პროფილი"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"კერძო სივრცე"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"კლონის შექმნა"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index b5756304e15f..176645fb1a9a 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Бір қолмен басқару режимі"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Экранды қарайту"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Есту аппараттары"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматты басу"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ажыратылды"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Қосылды"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Белсенді"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Дауыспен басқару арқылы қоңырау шалу үшін есту аппаратының микрофонын пайдалана аласыз. Микрофонға тек қоңырау кезінде ауысады."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Ауысу"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Параметрлер"</string>
- <string name="user_switched" msgid="7249833311585228097">"Ағымдағы пайдаланушы <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> профиліне ауысу…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу…"</string>
<string name="owner_name" msgid="8713560351570795743">"Құрылғы иесі"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Босату алдында бекітпесін ашу өрнегін сұрау"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Босату алдында құпия сөзді сұрау"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Әкімшіңіз орнатты.\nБерілген рұқсаттарды көру үшін параметрлерге өтіңіз."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Әкімші жаңартқан"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Әкімші жойған"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Жарайды"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Батареяны үнемдеу режимі қараңғы режимді іске қосады және фондық әрекеттерге, кейбір визуалдық әсерлерге, белгілі бір функциялар мен кейбір желі байланыстарына шектеу қояды немесе оларды өшіреді."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Айналдыру"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Кідірту"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Орналастыру"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ШЕКТЕЛГЕН себетке салынды."</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"сурет жіберілді"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Жұмыс 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Сынақ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Жалпы"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Жұмыс профилі"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Құпия кеңістік"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index eeae49bf388a..63b05e670513 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"មុខងារប្រើដៃម្ខាង"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ងងឹតខ្លាំង"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ចុចស្វ័យប្រវត្តិ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"បាន​ផ្ដាច់"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"បានភ្ជាប់"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"សកម្ម"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"អ្នកអាចប្រើមីក្រូហ្វូនឧបករណ៍​ជំនួយការ​ស្ដាប់របស់អ្នកសម្រាប់ការហៅទូរសព្ទដោយមិន​ប្រើ​ដៃ។ ការធ្វើបែបនេះប្ដូរមីក្រូហ្វូនរបស់អ្នក ក្នុងអំឡុងការហៅទូរសព្ទតែប៉ុណ្ណោះ។"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ប្ដូរ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ការកំណត់"</string>
- <string name="user_switched" msgid="7249833311585228097">"អ្នក​ប្រើ​បច្ចុប្បន្ន <xliff:g id="NAME">%1$s</xliff:g> ។"</string>
<string name="user_switching_message" msgid="1912993630661332336">"កំពុង​ប្ដូរ​ទៅ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"កំពុងចេញ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"ម្ចាស់"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"សួរ​រក​លំនាំ​ដោះ​សោ​មុន​ពេលដោះខ្ទាស់"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"សួរ​រក​ពាក្យ​សម្ងាត់​មុន​ពេល​ផ្ដាច់"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"បានដំឡើងដោយអ្នកគ្រប់គ្រងរបស់អ្នក។\nចូលទៅកាន់ការកំណត់ ដើម្បីមើលការ​អនុញ្ញាតដែលផ្ដល់ឱ្យ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"ធ្វើ​បច្ចុប្បន្នភាព​ដោយ​អ្នកគ្រប់គ្រង​របស់​អ្នក"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"លុប​ដោយ​អ្នកគ្រប់គ្រង​របស់​អ្នក"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"យល់ព្រម"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"មុខងារ​សន្សំថ្មបើកទម្រង់រចនាងងឹត និងដាក់កំហិត ឬបិទសកម្មភាពផ្ទៃខាងក្រោយ បែបផែនរូបភាពមួយចំនួន មុខងារជាក់លាក់ និងការតភ្ជាប់បណ្ដាញមួយចំនួន។"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"រំកិល"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ផ្អាក"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"ទីតាំង"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ត្រូវបានដាក់​ទៅក្នុងធុង​ដែលបានដាក់កំហិត"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>៖"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"បាន​ផ្ញើរូបភាព"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ការងារទី 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ការធ្វើ​តេស្ត"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"ទូទៅ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"កម្រងព័ត៌មានការងារ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"លំហ​ឯកជន"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ក្លូន"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index fd468b43fd33..f56f2c8571cb 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ಒಂದು ಕೈ ಮೋಡ್‌"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ಇನ್ನಷ್ಟು ಮಬ್ಬು"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ಆಟೋಕ್ಲಿಕ್"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ಡಿಸ್‌ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ಸಕ್ರಿಯವಾಗಿದೆ"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ಹ್ಯಾಂಡ್ಸ್-ಫ್ರೀ ಕರೆ ಮಾಡುವಿಕೆಗಾಗಿ ನಿಮ್ಮ ಶ್ರವಣ ಸಾಧನದ ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನೀವು ಬಳಸಬಹುದು. ಇದು ಕರೆಯ ಸಮಯದಲ್ಲಿ ಮಾತ್ರ ನಿಮ್ಮ ಮೈಕ್ ಅನ್ನು ಬದಲಾಯಿಸುತ್ತದೆ."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ಬದಲಿಸಿ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
- <string name="user_switched" msgid="7249833311585228097">"ಪ್ರಸ್ತುತ ಬಳಕೆದಾರರು <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ಗೆ ಬದಲಾಯಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ಅವರನ್ನು ಲಾಗ್‌ ಔಟ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
<string name="owner_name" msgid="8713560351570795743">"ಮಾಲೀಕರು"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ಅನ್‌ಪಿನ್ ಮಾಡಲು ಅನ್‌ಲಾಕ್ ಪ್ಯಾಟರ್ನ್ ಕೇಳಿ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ಅನ್‌ಪಿನ್ ಮಾಡಲು ಪಾಸ್‌ವರ್ಡ್ ಕೇಳು"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿದ್ದಾರೆ.\nನೀಡಲಾದ ಅನುಮತಿಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ಹೋಗಿ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರಿಂದ ಅಪ್‌ಡೇಟ್ ಮಾಡಲ್ಪಟ್ಟಿದೆ"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಅಪ್‌ಡೇಟ್ ಮಾಡಿದ್ದಾರೆ.\nನೀಡಲಾದ ಅನುಮತಿಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ಹೋಗಿ"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಅಳಿಸಿದ್ದಾರೆ"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ಸರಿ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ಬ್ಯಾಟರಿ ಸೇವರ್, ಡಾರ್ಕ್ ಥೀಮ್ ಅನ್ನು ಆನ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ಹಿನ್ನೆಲೆ ಚಟುವಟಿಕೆ, ಕೆಲವು ವಿಷುವಲ್ ಎಫೆಕ್ಟ್‌ಗಳು, ಕೆಲವು ಫೀಚರ್‌ಗಳು ಮತ್ತು ಇತರ ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕಗಳನ್ನು ಮಿತಿಗೊಳಿಸುತ್ತದೆ ಅಥವಾ ಆಫ್ ಮಾಡುತ್ತದೆ."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ವಿರಾಮಗೊಳಿಸಿ"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"ಸ್ಥಾನ"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ಬಂಧಿತ ಬಕೆಟ್‌ಗೆ ಹಾಕಲಾಗಿದೆ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ಚಿತ್ರವನ್ನು ಕಳುಹಿಸಲಾಗಿದೆ"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ಕೆಲಸ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ಪರೀಕ್ಷೆ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"ಸಮುದಾಯ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ಕ್ಲೋನ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 0a8867088ecb..253d162427fa 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"한 손 모드"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"더 어둡게"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"청각 보조 기기"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"자동 클릭"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"연결 끊김"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"연결됨"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"활성"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"보청기 마이크로 핸즈프리 통화 기능을 이용할 수 있습니다. 통화 중에만 마이크가 전환됩니다."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"전환"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"설정"</string>
- <string name="user_switched" msgid="7249833311585228097">"현재 사용자는 <xliff:g id="NAME">%1$s</xliff:g>님입니다."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>로 전환하는 중…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>님을 로그아웃하는 중…"</string>
<string name="owner_name" msgid="8713560351570795743">"소유자"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"고정 해제 시 잠금 해제 패턴 요청"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"고정 해제 이전에 비밀번호 요청"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"관리자에 의해 설치되었습니다.\n부여된 권한을 확인하려면 설정으로 이동하세요."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"관리자에 의해 업데이트되었습니다."</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"관리자에 의해 삭제되었습니다."</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"확인"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"절전 모드는 어두운 테마를 사용하고 백그라운드 활동, 일부 시각 효과, 특정 기능 및 일부 네트워크 연결을 제한하거나 사용 중지합니다."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"스크롤"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"일시중지"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"위치"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 항목이 RESTRICTED 버킷으로 이동함"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"이미지 보냄"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"직장 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"테스트"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"공동"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"직장 프로필"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"비공개 스페이스"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"클론"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 83a1c69afa92..96f01ffcb93a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Бир кол режими"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Кошумча караңгылатуу"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Угуу түзмөктөрү"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Авточыкылдатуу"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ажыратылды"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Туташты"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Жигердүү"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Угуу аппаратыңыздын микрофонун үн режиминде чалуу үчүн колдоно аласыз. Микрофонуңуз чалуу учурунда гана которулат."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Которулуу"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Параметрлер"</string>
- <string name="user_switched" msgid="7249833311585228097">"Учурдагы колдонуучу <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> дегенге которулууда…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> чыгууда…"</string>
<string name="owner_name" msgid="8713560351570795743">"Ээси"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Бошотуудан мурун графикалык ачкыч суралсын"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Бошотуудан мурун сырсөз суралсын"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Администраторуңуз орнотту.\nБерилген уруксаттарды көрүү үчүн параметрлерге өтүңүз"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Администраторуңуз жаңыртып койгон"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Администраторуңуз орнотту.\nБерилген уруксаттарды көрүү үчүн параметрлерге өтүңүз"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Администраторуңуз жок кылып салган"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ЖАРАЙТ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Батареяны үнөмдөөчү режимде Караңгы тема күйгүзүлүп, фондогу аракеттер, айрым визуалдык эффекттер, белгилүү бир функциялар жана айрым тармакка туташуулар чектелип же өчүрүлөт."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Сыдыруу"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Тындыруу"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Орду"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ЧЕКТЕЛГЕН чакага коюлган"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"сүрөт жөнөттү"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Жумуш 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Сыноо"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Жалпы"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Жумуш профили"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Жеке мейкиндик"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string>
@@ -2502,8 +2514,8 @@
<string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Күйгүзүү"</string>
<string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Артка кайтуу"</string>
<string name="unarchival_session_app_label" msgid="6811856981546348205">"Кезекте турат..."</string>
- <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Спутник SOS эми жеткиликтүү"</string>
- <string name="satellite_sos_available_notification_summary" msgid="1727088812951848330">"Мобилдик Интернет же Wi-Fi тармагы жок болсо, кырсыктаганда жардамга келчү кызматтарга билдирүү жөнөтө аласыз. Google Messages демейки жазышуу колдонмоңуз болушу керек."</string>
+ <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Спутник SOS кызматы иштеп баштады"</string>
+ <string name="satellite_sos_available_notification_summary" msgid="1727088812951848330">"Мобилдик Интернет же Wi-Fi жок болгон учурда, кырсыктаганда жардамга келчү кызматтарга билдирүү жөнөтө аласыз. Google Жазышуу демейки колдонмоңуз болушу керек."</string>
<string name="satellite_sos_not_supported_notification_title" msgid="2659100983227637285">"Спутник SOS колдоого алынбайт"</string>
<string name="satellite_sos_not_supported_notification_summary" msgid="1071762454665310549">"Бул түзмөктө спутник SOS колдоого алынбайт"</string>
<string name="satellite_sos_not_provisioned_notification_title" msgid="8564738683795406715">"Спутник SOS туураланган жок"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index f32ede63c67f..4905a53b06f6 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ໂໝດມືດຽວ"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ຫຼຸດແສງເປັນພິເສດ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ອຸປະກອນຊ່ວຍຟັງ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ການຄລິກອັດຕະໂນມັດ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ຕັດການເຊື່ອມຕໍ່ແລ້ວ"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ນຳໃຊ້ຢູ່"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ທ່ານສາມາດໃຊ້ໄມໂຄຣໂຟນຂອງເຄື່ອງຊ່ວຍຟັງເພື່ອການໂທແບບແຮນຟຣີໄດ້. ການດຳເນີນການນີ້ພຽງແຕ່ປ່ຽນໄມໂຄຣໂຟນຂອງທ່ານໃນລະຫວ່າງການໂທເທົ່ານັ້ນ."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ປ່ຽນ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ການຕັ້ງຄ່າ"</string>
- <string name="user_switched" msgid="7249833311585228097">"ຜູ່ໃຊ້ປັດຈຸບັນ <xliff:g id="NAME">%1$s</xliff:g> ."</string>
<string name="user_switching_message" msgid="1912993630661332336">"ກຳລັງສະຫຼັບໄປຫາ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"ກຳລັງອອກຈາກລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"ເຈົ້າຂອງ"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"​ຖາມ​ຫາ​ຮູບ​ແບບ​ປົດ​ລັອກ​ກ່ອນ​ຍົກ​ເລີກ​ການ​ປັກ​ໝຸດ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"​ຖາມ​ຫາ​ລະ​ຫັດ​ຜ່ານ​ກ່ອນ​ຍົກ​ເລີກ​ການ​ປັກ​ໝຸດ"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"ຕິດຕັ້ງໂດຍຜູ້ເບິ່ງແຍງຂອງທ່ານ.\nເຂົ້າໄປການຕັ້ງຄ່າເພື່ອເບິ່ງສິດທີ່ໄດ້ຮັບອະນຸຍາດ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"ຖືກອັບໂຫລດໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"ຖືກລຶບອອກໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ຕົກລົງ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ຕົວປະຢັດແບັດເຕີຣີຈະເປີດໃຊ້ຮູບແບບສີສັນມືດ ແລະ ຈຳກັດ ຫຼື ປິດການເຄື່ອນໄຫວໃນພື້ນຫຼັງ, ເອັບເຟັກທາງພາບຈຳນວນໜຶ່ງ, ຄຸນສົມບັດບາງຢ່າງ ແລະ ການເຊື່ອມຕໍ່ເຄືອຂ່າຍບາງອັນ."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"ເລື່ອນ"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ຢຸດຊົ່ວຄາວ"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"ຕຳແໜ່ງ"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ຖືກວາງໄວ້ໃນກະຕ່າ \"ຈຳກັດ\" ແລ້ວ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ສົ່ງຮູບແລ້ວ"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ວຽກ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ທົດສອບ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"ສ່ວນກາງ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ພື້ນທີ່ສ່ວນບຸກຄົນ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ໂຄລນ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 51311b379649..07a6c6e663d2 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Vienos rankos režimas"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Itin blanku"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Klausos įrenginiai"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatinis paspaudimas"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Atjungta"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Prisijungta"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktyvus"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Galite naudoti klausos aparato mikrofoną skambučiams laisvų rankų režimu. Mikrofonas įjungiamas tik per skambutį."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Perjungti"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nustatymai"</string>
- <string name="user_switched" msgid="7249833311585228097">"Dabartinis naudotojas: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Perjungiama į <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Atsijungiama (<xliff:g id="NAME">%1$s</xliff:g>)…"</string>
<string name="owner_name" msgid="8713560351570795743">"Savininkas"</string>
@@ -1969,7 +1967,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Prašyti atrakinimo piešinio prieš atsegant"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Prašyti slaptažodžio prieš atsegant"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Įdiegė administratorius.\nEikite į nustatymus ir peržiūrėkite suteiktus leidimus"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Atnaujino administratorius"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Atnaujino administratorius.\nEikite į nustatymus ir peržiūrėkite suteiktus leidimus"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Ištrynė administratorius"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Gerai"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Akumuliatoriaus tausojimo priemonė įjungia tamsiąją temą ir apriboja arba išjungia veiklą fone, kai kuriuos vaizdinius efektus, tam tikras funkcijas bei kai kuriuos tinklo ryšius."</string>
@@ -2277,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Slinkti"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pristabdyti"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozicija"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"„<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>“ įkeltas į grupę APRIBOTA"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"išsiuntė vaizdą"</string>
@@ -2484,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Darbas (3)"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Bandymas"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Bendruomenės"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Darbo profilis"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privati erdvė"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klonuoti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 3b3609e0b07b..60a99b3fd338 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Vienas rokas režīms"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Papildu aptumšošana"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dzirdes aparāti"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automātiskā klikšķināšana"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Atvienota"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Pievienota"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktīva"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Varat izmantot dzirdes aparāta mikrofonu zvaniem brīvroku režīmā. Mikrofons tiek pārslēgts tikai zvana laikā."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Pārslēgt"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Iestatījumi"</string>
- <string name="user_switched" msgid="7249833311585228097">"Pašreizējais lietotājs: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Notiek pāriešana uz: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Notiek lietotāja <xliff:g id="NAME">%1$s</xliff:g> atteikšanās…"</string>
<string name="owner_name" msgid="8713560351570795743">"Īpašnieks"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pirms atspraušanas pieprasīt atbloķēšanas kombināciju"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pirms atspraušanas pieprasīt paroli"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalēja jūsu administrators.\nPārejiet uz iestatījumiem, lai skatītu piešķirtās atļaujas."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Atjaunināja administrators"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Dzēsa administrators"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Labi"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Akumulatora enerģijas taupīšanas režīmā tiek ieslēgts tumšais motīvs un tiek ierobežotas vai izslēgtas darbības fonā, daži vizuālie efekti, noteiktas funkcijas un noteikti tīkla savienojumi."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Ritināt"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pārtraukt"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozīcija"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Pakotne “<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>” ir ievietota ierobežotā kopā."</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"nosūtīts attēls"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Darbam (3.)"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Testēšanai"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Kopīgs"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Darba profils"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privātā telpa"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klons"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0c92fd5cace6..6d98869e7b60 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1531,7 +1531,7 @@
<string name="ext_media_status_unmounted" msgid="8145812017295835941">"Исфрлено"</string>
<string name="ext_media_status_checking" msgid="159013362442090347">"Се проверува..."</string>
<string name="ext_media_status_mounted" msgid="3459448555811203459">"Подготвено"</string>
- <string name="ext_media_status_mounted_ro" msgid="1974809199760086956">"Само за читање"</string>
+ <string name="ext_media_status_mounted_ro" msgid="1974809199760086956">"Само за преглед"</string>
<string name="ext_media_status_bad_removal" msgid="508448566481406245">"Отстранет небезбедно"</string>
<string name="ext_media_status_unmountable" msgid="7043574843541087748">"Оштетено"</string>
<string name="ext_media_status_unsupported" msgid="5460509911660539317">"Неподдржано"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим со една рака"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Дополнително затемнување"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слушни помагала"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматско кликнување"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Не е поврзано"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Поврзано"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Може да го користите вашиот микрофон на слушното помагало за повикување без користење раце. Ова го префрла вашиот микрофон само за време на повикот."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Префрли"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Поставки"</string>
- <string name="user_switched" msgid="7249833311585228097">"Тековен корисник <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Се префрла на <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> се одјавува…"</string>
<string name="owner_name" msgid="8713560351570795743">"Сопственик"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Побарај шема за откл. пред откачување"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Побарај лозинка пред откачување"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Инсталирано од администраторот.\nОдете во „Поставки“ за да ги прегледате доделените дозволи"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ажурирано од администраторот"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Избришано од администраторот"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Во ред"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"„Штедачот на батерија“ вклучува „Темна тема“ и ограничува или исклучува активност во заднина, некои визуелни ефекти, одредени функции и некои мрежни врски."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Лизгање"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Паузирај"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Позиционирај"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> е ставен во корпата ОГРАНИЧЕНИ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"испрати слика"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Работен профил 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Профил за тестирање"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Профил на заедницата"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Работен профил"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватен простор"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клониран профил"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index cbdbd2d8108d..fca58a113c7b 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ഒറ്റക്കൈ മോഡ്"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"കൂടുതൽ ഡിം ചെയ്യൽ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ശ്രവണ ഉപകരണങ്ങൾ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ഓട്ടോക്ലിക്ക്"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"വിച്‌ഛേദിച്ചു"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"കണക്റ്റ് ചെയ്‌തു"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"സജീവം"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ഹാൻഡ്‌സ്‌-ഫ്രീ ആയി കോൾ ചെയ്യാൻ നിങ്ങളുടെ ശ്രവണ സഹായി മൈക്രോഫോൺ ഉപയോഗിക്കാം. ഇത് കോൾ സമയത്ത് മാത്രം നിങ്ങളുടെ മൈക്ക് മാറ്റുന്നു."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"മാറുക"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ക്രമീകരണം"</string>
- <string name="user_switched" msgid="7249833311585228097">"നിലവിലെ ഉപയോക്താവ് <xliff:g id="NAME">%1$s</xliff:g> ആണ്."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> എന്ന ഉപയോക്താവിലേക്ക് മാറുന്നു…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ലോഗൌട്ട് ചെയ്യുന്നു…"</string>
<string name="owner_name" msgid="8713560351570795743">"ഉടമ"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"അൺപിന്നിനുമുമ്പ് അൺലോക്ക് പാറ്റേൺ ആവശ്യപ്പെടൂ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"അൺപിന്നിനുമുമ്പ് പാസ്‌വേഡ് ആവശ്യപ്പെടൂ"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"നിങ്ങളുടെ അഡ്‌മിൻ ഇൻസ്‌റ്റാൾ ചെയ്തത്.\nനൽകിയ അനുമതികൾ കാണാൻ ക്രമീകരണത്തിലേക്ക് പോകുക"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"നിങ്ങളുടെ അഡ്‌മിൻ അപ്‌ഡേറ്റ് ചെയ്യുന്നത്"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"നിങ്ങളുടെ അഡ്‌മിൻ അപ്‌ഡേറ്റ് ചെയ്തത്.\nനൽകിയ അനുമതികൾ കാണാൻ ക്രമീകരണത്തിലേക്ക് പോകുക"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"നിങ്ങളുടെ അഡ്‌മിൻ ഇല്ലാതാക്കുന്നത്"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ശരി"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"\'ബാറ്ററി സേവർ\' ഡാർക്ക് തീം ഓണാക്കുന്നു, ഒപ്പം പശ്ചാത്തല ആക്‌റ്റിവിറ്റിയും ചില വിഷ്വൽ ഇഫക്റ്റുകളും ചില ഫീച്ചറുകളും ചില നെറ്റ്‌വർക്ക് കണക്ഷനുകളും പരിമിതപ്പെടുത്തുകയോ ഓഫാക്കുകയോ ചെയ്യുന്നു."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"സ്‌ക്രോൾ ചെയ്യുക"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"താൽക്കാലികമായി നിർത്തുക"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"സ്ഥാനം"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> നിയന്ത്രിത ബക്കറ്റിലേക്ക് നീക്കി"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ചിത്രം അയച്ചു"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ഔദ്യോഗികം 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ടെസ്‌റ്റ്"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"കമ്മ്യൂണൽ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"സ്വകാര്യ സ്പേസ്"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ക്ലോൺ ചെയ്യുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 4c72a86dcc75..58f4cf7b83cb 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Нэг гарын горим"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Хэт бүүдгэр"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Сонсголын төхөөрөмжүүд"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Автомат товшилт"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Салсан"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Холбогдсон"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Идэвхтэй"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Та гараас хамаарахгүй дуудлагад сонсголын төхөөрөмжийнхөө микрофоныг ашиглаж болно. Энэ нь зөвхөн дуудлагын үеэр таны микрофоныг сэлгэнэ."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Сэлгэх"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Тохиргоо"</string>
- <string name="user_switched" msgid="7249833311585228097">"Одоогийн хэрэглэгч <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> руу сэлгэж байна…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-с гарч байна…"</string>
<string name="owner_name" msgid="8713560351570795743">"Өмчлөгч"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Бэхэлснийг болиулахаас өмнө түгжээ тайлах хээ асуух"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тогтоосныг суллахаас өмнө нууц үг асуух"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Танай админ суулгасан.\nОлгосон зөвшөөрлүүдийг харахын тулд тохиргоо руу очно уу"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Таны админ шинэчилсэн"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Таны админ устгасан"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Батарей хэмнэгч нь Бараан загварыг асааж, дэвсгэрийн үйл ажиллагаа, зарим визуал эффект, тодорхой онцлогууд болон зарим сүлжээний холболтыг хязгаарлах эсвэл унтраана."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Гүйлгэх"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Түр зогсоох"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Байрлал"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>-г ХЯЗГААРЛАСАН сагс руу орууллаа"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"зураг илгээсэн"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Ажил 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Туршилт"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Нийтийн"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Ажлын профайл"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Хаалттай орон зай"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string>
@@ -2502,7 +2515,7 @@
<string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Асаах"</string>
<string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Буцах"</string>
<string name="unarchival_session_app_label" msgid="6811856981546348205">"Хүлээгдэж буй..."</string>
- <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Хиймэл дагуул SOS одоо боломжтой боллоо"</string>
+ <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Хиймэл дагуулын SOS одоо боломжтой боллоо"</string>
<string name="satellite_sos_available_notification_summary" msgid="1727088812951848330">"Та хөдөлгөөнт холбооны эсвэл Wi-Fi сүлжээ байхгүй бол яаралтай тусламжийн үйлчилгээ рүү мессеж бичих боломжтой. Google Мессеж таны өгөгдмөл мессеж апп байх ёстой."</string>
<string name="satellite_sos_not_supported_notification_title" msgid="2659100983227637285">"Хиймэл дагуул SOS-г дэмждэггүй"</string>
<string name="satellite_sos_not_supported_notification_summary" msgid="1071762454665310549">"Хиймэл дагуул SOS-г энэ төхөөрөмж дээр дэмждэггүй"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index ea1e92baf387..c37884d4473c 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -115,8 +115,8 @@
<string name="roamingText2" msgid="2834048284153110598">"रोमिंग दर्शक फ्लॅशिंग"</string>
<string name="roamingText3" msgid="831690234035748988">"अतिपरिचित क्षेत्राबाहेर"</string>
<string name="roamingText4" msgid="2171252529065590728">"इमारती बाहेर"</string>
- <string name="roamingText5" msgid="4294671587635796641">"रोमिंग - प्राधान्यीकृत सिस्टम"</string>
- <string name="roamingText6" msgid="5536156746637992029">"रोमिंग - उपलब्ध सिस्टम"</string>
+ <string name="roamingText5" msgid="4294671587635796641">"रोमिंग - प्राधान्य दिलेली सिस्टीम"</string>
+ <string name="roamingText6" msgid="5536156746637992029">"रोमिंग - उपलब्ध सिस्टीम"</string>
<string name="roamingText7" msgid="1783303085512907706">"रोमिंग - युती भागीदार"</string>
<string name="roamingText8" msgid="7774800704373721973">"रोमिंग - प्रीमियम भागीदार"</string>
<string name="roamingText9" msgid="1933460020190244004">"रोमिंग - पूर्ण सेवा कार्यक्षमता"</string>
@@ -242,7 +242,7 @@
<string name="silent_mode_silent" msgid="5079789070221150912">"रिंगर बंद"</string>
<string name="silent_mode_vibrate" msgid="8821830448369552678">"रिंगर व्हायब्रेट"</string>
<string name="silent_mode_ring" msgid="6039011004781526678">"रिंगर सुरू"</string>
- <string name="reboot_to_update_title" msgid="2125818841916373708">"Android सिस्टम अपडेट"</string>
+ <string name="reboot_to_update_title" msgid="2125818841916373708">"Android सिस्टीम अपडेट"</string>
<string name="reboot_to_update_prepare" msgid="6978842143587422365">"अपडेट करण्याची तयारी करत आहे…"</string>
<string name="reboot_to_update_package" msgid="4644104795527534811">"अपडेट पॅकेज प्रक्रिया करत आहे…"</string>
<string name="reboot_to_update_reboot" msgid="4474726009984452312">"रीस्टार्ट करत आहे..."</string>
@@ -274,7 +274,7 @@
<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_summary" msgid="1975130009258435885">"तुमचे डिव्हाइस प्रतिसाद देत नाही किंवा खूप धीमे असते अथवा तुम्हाला सर्व अहवाल विभागांची आवश्यकता असते तेव्हा कमीतकमी सिस्टम हस्तक्षेपासाठी या पर्यायाचा वापर करा. तुम्हाला आणखी तपशील एंटर करण्याची किंवा अतिरिक्त स्क्रीनशॉट घेण्याची अनुमती देत नाही."</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>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"बग रिपोर्टसह स्क्रीनशॉट घेता आला नाही"</string>
@@ -374,7 +374,7 @@
<string name="dream_preview_title" msgid="5570751491996100804">"पूर्वावलोकन, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string>
<string name="dream_accessibility_action_click" msgid="7392398629967797805">"डिसमिस करा"</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="permlab_expandStatusBar" msgid="1184232794782141698">"स्‍टेटस बार विस्तृत करा/संकुचित करा"</string>
@@ -472,9 +472,9 @@
<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="tv" msgid="4900842256047614307">"सिस्टम बूट होणे संपल्यावर ॲपला स्वतः सुरू होण्याची अनुमती देते. यामुळे तुमच्या Android TV डिव्हाइसला सुरू होण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर डिव्हाइसलाच धीमे करण्याची अनुमती ॲपला देते."</string>
- <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"जसे सिस्टम बूट करणे समाप्त करते तसे अ‍ॅप ला स्वतः सुरू करण्यास अनुमती देते. यामुळे फोन सुरू करण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर फोनला धीमे करण्यास अ‍ॅप ला अनुमती देते."</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="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>
@@ -529,8 +529,8 @@
<string name="permdesc_camera" msgid="5240801376168647151">"ॲप वापरात असताना, हे ॲप कॅमेरा वापरून फोटो काढू शकते आणि व्हिडिओ रेकॉर्ड करू शकते."</string>
<string name="permlab_backgroundCamera" msgid="7549917926079731681">"बॅकग्राउंडमध्ये फोटो काढा आणि व्हिडिओ रेकॉर्ड करा"</string>
<string name="permdesc_backgroundCamera" msgid="1615291686191138250">"हे ॲप कॅमेरा वापरून कधीही फोटो काढू शकते आणि व्हिडिओ रेकॉर्ड करू शकते."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"फोटो आणि व्हिडिओ काढण्यासाठी ॲप्लिकेशन किंवा सेवेला सिस्टम कॅमेरे ॲक्सेस करण्याची अनुमती द्या"</string>
- <string name="permdesc_systemCamera" msgid="5938360914419175986">"हे विशेषाधिकृत किंवा सिस्टम ॲप कधीही सिस्टम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. ॲपकडे android.permission.CAMERA परवानगी असण्याचीदेखील आवश्यकता आहे"</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"फोटो आणि व्हिडिओ काढण्यासाठी ॲप्लिकेशन किंवा सेवेला सिस्टीम कॅमेरे ॲक्सेस करण्याची अनुमती द्या"</string>
+ <string name="permdesc_systemCamera" msgid="5938360914419175986">"हे विशेष महत्त्वाचे किंवा सिस्टीम ॲप कधीही सिस्टीम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. ॲपकडे android.permission.CAMERA परवानगी असण्याचीदेखील आवश्यकता आहे"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"एखाद्या अ‍ॅप्लिकेशन किंवा सेवेला कॅमेरा डिव्हाइस सुरू किंवा बंद केल्याची कॉलबॅक मिळवण्याची अनुमती द्या."</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"कोणतेही कॅमेरा डिव्हाइस (कोणत्या अ‍ॅप्लिकेशनने) सुरू किंवा बंद केले जाते तेव्हा हे ॲप कॉलबॅक मिळवू शकते."</string>
<string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"अ‍ॅप्लिकेशन किंवा सेवेला हेडलेस सिस्टीम वापरकर्ता म्हणून कॅमेरा अ‍ॅक्सेस करण्याची अनुमती द्या."</string>
@@ -548,7 +548,7 @@
<string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"यामुळे ॲपला डिव्हाइसची मूलभूत टेलिफोनी वैशिष्ट्ये अ‍ॅक्सेस करण्याची अनुमती मिळते."</string>
<string name="permlab_manageOwnCalls" msgid="9033349060307561370">"प्रणालीच्या माध्यमातून कॉल रूट करा"</string>
<string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"कॉल करण्याचा अनुभव सुधारण्यासाठी ॲपला त्याचे कॉल प्रणालीच्या माध्यमातून रूट करू देते."</string>
- <string name="permlab_callCompanionApp" msgid="3654373653014126884">"सिस्टम वापरून कॉल पहा आणि नियंत्रण ठेवा."</string>
+ <string name="permlab_callCompanionApp" msgid="3654373653014126884">"सिस्टीम वापरून कॉल पहा आणि नियंत्रण ठेवा."</string>
<string name="permdesc_callCompanionApp" msgid="8474168926184156261">"डिव्हाइसवर येणार कॉल पाहण्यासाठी आणि नियंत्रित करण्यासाठी ॲपला अनुमती देते. यामध्ये कॉल करण्यासाठी कॉलचा नंबर आणि कॉलची स्थिती यासारख्या माहितीचा समावेश असतो."</string>
<string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"ऑडिओ रेकॉर्ड प्रतिबंधांपासून मुक्त"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"ऑडिओ रेकॉर्ड करण्यासाठी प्रतिबंधांपासून ॲपला मुक्त करा."</string>
@@ -569,11 +569,11 @@
<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_accessHiddenProfile" msgid="8607094418491556823">"लपवलेल्या प्रोफाइल अ‍ॅक्सेस करा"</string>
<string name="permdesc_accessHiddenProfile" msgid="1543153202481009676">"अ‍ॅपला लपवलेल्या प्रोफाइल अ‍ॅक्सेस करण्याची अनुमती देते."</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>
@@ -1241,7 +1241,7 @@
<string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"इनपुट पद्धत पिकर उघडा"</string>
<string name="input_method_switcher_settings_button" msgid="5609835654697108485">"सेटिंग्ज"</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" msgid="8172166728369697835">"काही सिस्टीम फंक्शन कार्य करू शकत नाहीत"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"सिस्टीमसाठी पुरेसे संचयन नाही. आपल्याकडे 250MB मोकळे स्थान असल्याचे सुनिश्चित करा आणि रीस्टार्ट करा."</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>
@@ -1287,7 +1287,7 @@
<string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"इमेज कॅप्चर करा"</string>
<string name="alwaysUse" msgid="3153558199076112903">"या क्रियेसाठी डीफॉल्‍टनुसार वापरा."</string>
<string name="use_a_different_app" msgid="4987790276170972776">"एक भिन्न अ‍ॅप वापरा"</string>
- <string name="clearDefaultHintMsg" msgid="1325866337702524936">"डाउनलोड केलेल्या सिस्टम सेटिंग्ज &gt; Apps &gt; मधील डीफॉल्ट साफ करा."</string>
+ <string name="clearDefaultHintMsg" msgid="1325866337702524936">"डाउनलोड केलेल्या सिस्टीम सेटिंग्ज &gt; Apps &gt; मधील डीफॉल्ट साफ करा."</string>
<string name="chooseActivity" msgid="8563390197659779956">"क्रिया निवडा"</string>
<string name="chooseUsbActivity" msgid="2096269989990986612">"USB डिव्हाइससाठी अ‍ॅप निवडा"</string>
<string name="noApplications" msgid="1186909265235544019">"कोणतेही अ‍ॅप्स ही क्रिया करू शकत नाहीत."</string>
@@ -1315,7 +1315,7 @@
<string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> मूळतः लाँच केले."</string>
<string name="screen_compat_mode_scale" msgid="8627359598437527726">"स्केल"</string>
<string name="screen_compat_mode_show" msgid="5080361367584709857">"नेहमी दर्शवा"</string>
- <string name="screen_compat_mode_hint" msgid="4032272159093750908">"सिस्टम सेटिंग्ज &gt; Apps &gt; डाउनलोड केलेले मध्ये हे पुन्हा-सुरू करा."</string>
+ <string name="screen_compat_mode_hint" msgid="4032272159093750908">"सिस्टीम सेटिंग्ज &gt; Apps &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>
@@ -1330,7 +1330,7 @@
<string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android सुरू करत आहे…"</string>
<string name="android_start_title" product="tablet" msgid="4429767260263190344">"टॅबलेट सुरू होत आहे…"</string>
<string name="android_start_title" product="device" msgid="6967413819673299309">"डिव्‍हाइस सुरू होत आहे…"</string>
- <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"सिस्‍टम अपडेट संपत आहे…"</string>
+ <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"सिस्टीम अपडेट संपत आहे…"</string>
<string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करत आहे…"</string>
<string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करत आहे."</string>
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"अ‍ॅप्स सुरू करत आहे."</string>
@@ -1708,7 +1708,7 @@
<string name="default_audio_route_name_external_device" msgid="8124229858618975">"बाह्य डिव्हाइस"</string>
<string name="default_audio_route_name_headphones" msgid="6954070994792640762">"हेडफोन"</string>
<string name="default_audio_route_name_usb" msgid="895668743163316932">"USB"</string>
- <string name="default_audio_route_category_name" msgid="5241740395748134483">"सिस्टम"</string>
+ <string name="default_audio_route_category_name" msgid="5241740395748134483">"सिस्टीम"</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">"कास्‍ट करा"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"एकहाती मोड"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"आणखी डिम"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"श्रवणयंत्रे"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ऑटोक्लिक"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"डिस्कनेक्ट केले आहे"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"कनेक्ट केले आहे"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"अ‍ॅक्टिव्ह"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"हँड्स-फ्री कॉलिंगसाठी तुम्ही तुमच्या श्रवणयंत्राचा मायक्रोफोन वापरू शकता. यामुळे कॉलदरम्यान फक्त तुमचा माइक स्विच केला जातो."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"स्विच करा"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"सेटिंग्ज"</string>
- <string name="user_switched" msgid="7249833311585228097">"वर्तमान वापरकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> वर स्विच करत आहे…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> लॉग आउट करत आहे…"</string>
<string name="owner_name" msgid="8713560351570795743">"मालक"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"अनपिन करण्‍यापूर्वी अनलॉक नमुन्यासाठी विचारा"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"अनपिन करण्‍यापूर्वी संकेतशब्दासाठी विचारा"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"तुमच्या ॲडमिनने इंस्टॉल केले आहे.\nदिलेल्या परवानग्या पाहण्यासाठी सेटिंग्जवर जा"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"आपल्या प्रशासकाने अपडेट केले"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"आपल्या प्रशासकाने हटवले"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ओके"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"बॅटरी सेव्हर गडद थीम सुरू करते आणि बॅकग्राउंड ॲक्टिव्हिटी, काही व्हिज्युअल इफेक्ट, ठरावीक वैशिष्ट्ये व काही नेटवर्क कनेक्शन मर्यादित किंवा बंद करते."</string>
@@ -2192,12 +2191,12 @@
<string name="screenshot_edit" msgid="7408934887203689207">"संपादित करा"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कॉल आणि सूचनांवर व्हायब्रेट होईल"</string>
<string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"कॉल आणि सूचना म्यूट केल्या जातील"</string>
- <string name="notification_channel_system_changes" msgid="2462010596920209678">"सिस्टम बदल"</string>
+ <string name="notification_channel_system_changes" msgid="2462010596920209678">"सिस्टीम बदल"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"सूचना सेटिंग्जचे पुनरावलोकन करा"</string>
<string name="review_notification_settings_text" msgid="5916244866751849279">"Android 13 पासून, तुम्ही त्यामध्ये इंस्टॉल केलेल्या अ‍ॅप्सना सूचना पाठवण्यासाठी तुमच्या परवानगीची आवश्यकता आहे. सध्याच्या अ‍ॅप्ससाठी ही परवानगी बदलण्याकरिता टॅप करा."</string>
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"मला आठवण करून द्या"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"डिसमिस करा"</string>
- <string name="notification_app_name_system" msgid="3045196791746735601">"सिस्टम"</string>
+ <string name="notification_app_name_system" msgid="3045196791746735601">"सिस्टीम"</string>
<string name="notification_app_name_settings" msgid="9088548800899952531">"सेटिंग्ज"</string>
<string name="notification_appops_camera_active" msgid="8177643089272352083">"कॅमेरा"</string>
<string name="notification_appops_microphone_active" msgid="581333393214739332">"मायक्रोफोन"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"स्क्रोल करा"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"थांबवा"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"स्थिती"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> हे प्रतिबंधित बादलीमध्ये ठेवण्यात आले आहे"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"इमेज पाठवली आहे"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ऑफिस ३"</string>
<string name="profile_label_test" msgid="9168641926186071947">"चाचणी"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"सामुदायिक"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"कार्य प्रोफाइल"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"खाजगी स्पेस"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 43acbac72819..d675246df729 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mod sebelah tangan"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Amat malap"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Peranti pendengaran"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoklik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Diputuskan sambungan"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Disambungkan"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktif"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Anda boleh menggunakan mikrofon alat bantu pendengaran anda untuk membuat panggilan bebas tangan. Tindakan ini hanya menukar mikrofon anda semasa panggilan."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Tukar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Tetapan"</string>
- <string name="user_switched" msgid="7249833311585228097">"Pengguna semasa <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Beralih kepada <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Log keluar daripada <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Pemilik"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Minta corak buka kunci sebelum menyahsemat"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Minta kata laluan sebelum menyahsemat"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Dipasang oleh pentadbir anda.\nAkses tetapan untuk melihat kebenaran yang diberikan"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Dikemas kini oleh pentadbir anda"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Dipadamkan oleh pentadbir anda"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Penjimat Bateri menghidupkan tema Gelap dan mengehadkan atau mematikan aktiviti latar, sesetengah kesan visual, ciri tertentu dan sesetengah sambungan rangkaian."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Tatal"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Jeda"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Kedudukan"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> telah diletakkan dalam baldi TERHAD"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"menghantar imej"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Kerja 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Ujian"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang persendirian"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 2333539d415c..1a5bc7af86ca 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"လက်တစ်ဖက်သုံးမုဒ်"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ပိုမှိန်ခြင်း"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"နားကြားကိရိယာ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"အော်တိုနှိပ်ခြင်း"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ချိတ်ဆက်မထားပါ"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"ချိတ်ဆက်ထားသည်"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"သုံးနေသည်"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"လက်လွတ်ခေါ်ဆိုခြင်းအတွက် နားကြားကိရိယာ၏ မိုက်ခရိုဖုန်းကို သုံးနိုင်သည်။ ခေါ်ဆိုနေစဉ်သာ သင့်မိုက်ကို ပြောင်းပေးသည်။"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ပြောင်းရန်"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ဆက်တင်များ"</string>
- <string name="user_switched" msgid="7249833311585228097">"လက်ရှိအသုံးပြုနေသူ <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>သို့ ပြောင်းနေသည်…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ကို ထွက်ပစ်ပါတော့မည်..."</string>
<string name="owner_name" msgid="8713560351570795743">"ပိုင်ရှင်"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ပင်မဖြုတ်မီ လော့ခ်ဖွင့်ပုံစံကို မေးရန်"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ပင်မဖြုတ်မီမှာ စကားဝှက်ကို မေးကြည့်ရန်"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"သင့်စီမံခန့်ခွဲသူက ထည့်သွင်းထားသည်။\nပေးထားသည့် ခွင့်ပြုချက်များကို ကြည့်ရန် ဆက်တင်များသို့ သွားပါ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"သင်၏ စီမံခန့်ခွဲသူက အပ်ဒိတ်လုပ်ထားသည်"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"သင်၏ စီမံခန့်ခွဲသူက ဖျက်လိုက်ပါပြီ"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"‘ဘက်ထရီ အားထိန်း’ က ‘အမှောင်နောက်ခံ’ ကို ဖွင့်ပြီး နောက်ခံလုပ်ဆောင်ချက်၊ ဖန်တီးပြသချက်အချို့၊ ဝန်ဆောင်မှုအချို့နှင့် ကွန်ရက်ချိတ်ဆက်မှုအချို့တို့ကို ကန့်သတ်သည် သို့မဟုတ် ပိတ်သည်။"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"လှိမ့်ရန်"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ခဏရပ်ရန်"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"နေရာ"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ကို တားမြစ်ထားသော သိမ်းဆည်းမှုအတွင်းသို့ ထည့်ပြီးပါပြီ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>-"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ပုံပို့ထားသည်"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"အလုပ် ၃"</string>
<string name="profile_label_test" msgid="9168641926186071947">"စမ်းသပ်မှု"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"အများသုံး"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"အလုပ်ပရိုဖိုင်"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"သီးသန့်နေရာ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ပုံတူပွားရန်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3a337f603348..4b44dd6c67ce 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enhåndsmodus"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra dimmet"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Høreapparater"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoklikk"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Frakoblet"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Tilkoblet"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kan bruke mikrofonen i høreapparatet til å ringe håndfritt. Dette fører bare til at mikrofonen byttes under samtalen."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Bytt"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Innstillinger"</string>
- <string name="user_switched" msgid="7249833311585228097">"Gjeldende bruker: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Bytter til <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logger av <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="owner_name" msgid="8713560351570795743">"Eier"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Krev opplåsingsmønster for å løsne apper"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Krev passord for å løsne apper"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installert av administratoren din.\nGå til innstillingene for å se hvilke tillatelser som er gitt"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Oppdatert av administratoren din"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Slettet av administratoren din"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Batterisparing slår på mørkt tema og begrenser eller slår av bakgrunnsaktivitet, enkelte visuelle effekter, noen funksjoner og noen nettverkstilkoblinger."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Rull"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Sett på pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Plassér"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> er blitt plassert i TILGANGSBEGRENSET-toppmappen"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"har sendt et bilde"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Jobb 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Felles"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Jobbprofil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index ce64589a4870..16e48d547a55 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"एक हाते मोड"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"अझै मधुरो"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"हियरिङ डिभाइसहरू"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"अटोक्लिक"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"डिस्कनेक्ट गरिएको"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"कनेक्ट गरिएको"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"सक्रिय"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"तपाईं ह्यान्ड्सफ्री तरिकाले कल गर्न आफ्नो श्रवण यन्त्रको माइक्रोफोन प्रयोग गर्न सक्नुहुन्छ। यसले कल भइरहेका बेला मात्र तपाईंको माइक बदल्छ।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"बदल्नुहोस्"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"सेटिङ"</string>
- <string name="user_switched" msgid="7249833311585228097">"अहिलेको प्रयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> मा स्विच गरिँदै छ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"लग आउट गर्दै <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"मालिक"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"अनपिन गर्नअघि अनलक प्याटर्न माग्नुहोस्"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"पिन निकाल्नुअघि पासवर्ड सोध्नुहोस्"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"यो प्याकेज तपाईंका एड्मिनले इन्स्टल गर्नुभएको हो।\nप्रदान गरिएका अनुमतिसम्बन्धी जानकारी हेर्न सेटिङमा जानुहोस्"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"तपाईंका प्रशासकले अद्यावधिक गर्नुभएको"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"तपाईंका प्रशासकले मेट्नुभएको"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ठिक छ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ब्याट्री सेभरले अँध्यारो थिम अन गर्छ र ब्याकग्राउन्डमा हुने क्रियाकलाप, केही भिजुअल इफेक्ट, निश्चित सुविधा र केही नेटवर्क कनेक्सनहरू अफ गर्छ वा सीमित रूपमा मात्र चल्न दिन्छ।"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"स्क्रोल गर्नुहोस्"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"पज गर्नुहोस्"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"स्थिति"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> लाई प्रतिबन्धित बाल्टीमा राखियो"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"फोटो पठाइयो"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"कार्य प्रोफाइल ३"</string>
<string name="profile_label_test" msgid="9168641926186071947">"परीक्षण"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"सामुदायिक"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"कार्य प्रोफाइल"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"निजी स्पेस"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index f7b6cc87249b..11c40fdbfce4 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Bediening met 1 hand"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dimmen"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hoortoestellen"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatisch klikken"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Verbinding verbroken"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Verbonden"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Actief"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Je kunt de microfoon van je hoortoestel gebruiken om handsfree te bellen. Hiermee wordt de microfoon alleen gewisseld tijdens het gesprek."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Wisselen"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Instellingen"</string>
- <string name="user_switched" msgid="7249833311585228097">"Huidige gebruiker <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Overschakelen naar <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> uitloggen…"</string>
<string name="owner_name" msgid="8713560351570795743">"Eigenaar"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ontgrendelingspatroon vragen om app los te maken"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vraag wachtwoord voor losmaken"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Geïnstalleerd door je beheerder.\nGa naar instellingen om verleende rechten te bekijken."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Geüpdatet door je beheerder"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Verwijderd door je beheerder"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Met Batterijbesparing wordt het donkere thema aangezet en worden achtergrondactiviteit, bepaalde visuele effecten, bepaalde functies en sommige netwerkverbindingen beperkt of uitgezet."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scrollen"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pauzeren"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Positie"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> is in de bucket RESTRICTED geplaatst"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"heeft een afbeelding gestuurd"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Werk 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Gemeenschappelijk"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Werkprofiel"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privégedeelte"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index ef567e504e70..eca778a24c8a 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ଏକ-ହାତ ମୋଡ୍"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ଅତ୍ୟଧିକ ଡିମ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ଶ୍ରବଣ ଡିଭାଇସଗୁଡ଼ିକ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ଅଟୋକ୍ଲିକ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ଡିସକନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"କନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ସକ୍ରିୟ"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ହେଣ୍ଡସ-ଫ୍ରି କଲିଂ ପାଇଁ ଆପଣ ଆପଣଙ୍କର ଶ୍ରବଣ ଯନ୍ତ୍ର ମାଇକ୍ରୋଫୋନ ବ୍ୟବହାର କରିପାରିବେ। କଲ ସମୟରେ ଏହା କେବଳ ଆପଣଙ୍କର ମାଇକକୁ ସୁଇଚ କରିଥାଏ।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ସୁଇଚ କରନ୍ତୁ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ସେଟିଂସ"</string>
- <string name="user_switched" msgid="7249833311585228097">"ବର୍ତ୍ତମାନର ୟୁଜର୍‌ ହେଉଛନ୍ତି <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ରେ ସୁଇଚ ହେଉଛି…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ଙ୍କୁ ଲଗଆଉଟ୍‍ କରାଯାଉଛି…"</string>
<string name="owner_name" msgid="8713560351570795743">"ମାଲିକ"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ଅନପିନ୍‌ କରିବା ପୂର୍ବରୁ ଲକ୍‌ ଖୋଲିବା ପାଟର୍ନ ପଚାରନ୍ତୁ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ଅନପିନ୍‌ କରିବା ପୂର୍ବରୁ ପାସ୍‌ୱର୍ଡ ପଚାରନ୍ତୁ"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଇନଷ୍ଟଲ କରାଯାଇଛି।\nଅନୁମୋଦିତ ଅମୁମତିଗୁଡ଼ିକ ଭ୍ୟୁ କରିବା ପାଇଁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"ଆପଣଙ୍କ ଆଡମିନ୍‌‌ ଅପଡେଟ୍‍ କରିଛନ୍ତି"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"ଆପଣଙ୍କ ଆଡମିନ୍‌‌ ଡିଲିଟ୍‍ କରିଛନ୍ତି"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ଠିକ ଅଛି"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ବେଟେରୀ ସେଭର ଗାଢ଼ା ଥିମକୁ ଚାଲୁ କରେ ଏବଂ ପୃଷ୍ଠପଟ କାର୍ଯ୍ୟକଳାପ, କିଛି ଭିଜୁଆଲ ଇଫେକ୍ଟ, କିଛି ଫିଚର ଏବଂ କିଛି ନେଟୱାର୍କ ସଂଯୋଗକୁ ସୀମିତ କିମ୍ବା ବନ୍ଦ କରେ।"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"ସ୍କ୍ରୋଲ କରନ୍ତୁ"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ବିରତ କରନ୍ତୁ"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"ସ୍ଥିତି"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>କୁ ପ୍ରତିବନ୍ଧିତ ବକେଟରେ ରଖାଯାଇଛି"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ଏକ ଛବି ପଠାଯାଇଛି"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ୱାର୍କ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ଟେଷ୍ଟ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"କମ୍ୟୁନାଲ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ୱାର୍କ ପ୍ରୋଫାଇଲ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ପ୍ରାଇଭେଟ ସ୍ପେସ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"କ୍ଲୋନ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 7f06227ba147..2da2a608bf97 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ਇੱਕ ਹੱਥ ਮੋਡ"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ਸੁਣਨ ਵਾਲੇ ਡੀਵਾਈਸ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ਸਵੈ-ਕਲਿੱਕ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"ਕਨੈਕਟ ਹੈ"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ਕਿਰਿਆਸ਼ੀਲ"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ਤੁਸੀਂ ਹੱਥ-ਰਹਿਤ ਕਾਲਿੰਗ ਲਈ ਆਪਣੇ ਸੁਣਨ ਦੇ ਸਾਧਨ ਦੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੇ ਹੋ। ਇਹ ਸਿਰਫ਼ ਕਾਲ ਦੌਰਾਨ ਹੀ ਤੁਹਾਡੇ ਮਾਈਕ ਨੂੰ ਸਵਿੱਚ ਕਰਦਾ ਹੈ।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ਸਵਿੱਚ ਕਰੋ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ਸੈਟਿੰਗਾਂ"</string>
- <string name="user_switched" msgid="7249833311585228097">"ਮੌਜੂਦਾ ਉਪਭੋਗਤਾ <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ਨੂੰ ਲਾਗ-ਆਉਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ …"</string>
<string name="owner_name" msgid="8713560351570795743">"ਮਾਲਕ"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ਅਨਪਿੰਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਅਣਲਾਕ ਪੈਟਰਨ ਵਾਸਤੇ ਪੁੱਛੋ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ਅਣਪਿੰਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪਾਸਵਰਡ ਮੰਗੋ"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਸਥਾਪਤ ਕੀਤਾ ਗਿਆ।\nਦਿੱਤੀਆਂ ਗਈਆਂ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਅੱਪਡੇਟ ਕੀਤਾ ਗਿਆ"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਮਿਟਾਇਆ ਗਿਆ"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ਠੀਕ ਹੈ"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ਬੈਟਰੀ ਸੇਵਰ ਗੂੜ੍ਹੇ ਥੀਮ ਨੂੰ ਚਾਲੂ ਕਰਦਾ ਹੈ ਅਤੇ ਬੈਕਗ੍ਰਾਊਂਡ ਸਰਗਰਮੀ, ਕੁਝ ਦ੍ਰਿਸ਼ਟੀਗਤ ਪ੍ਰਭਾਵਾਂ, ਕੁਝ ਖਾਸ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਅਤੇ ਕੁਝ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨਾਂ ਨੂੰ ਸੀਮਤ ਜਾਂ ਬੰਦ ਕਰਦਾ ਹੈ।"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"ਸਕ੍ਰੋਲ ਕਰੋ"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"ਰੋਕੋ"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"ਸਥਿਤੀ"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ਨੂੰ ਪ੍ਰਤਿਬੰਧਿਤ ਖਾਨੇ ਵਿੱਚ ਪਾਇਆ ਗਿਆ ਹੈ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ਚਿੱਤਰ ਭੇਜਿਆ ਗਿਆ"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ਕੰਮ ਸੰਬੰਧੀ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ਜਾਂਚ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"ਭਾਈਚਾਰਕ"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ਕਲੋਨ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 73e4d24ac241..730429da67d2 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1715,7 +1715,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Wyświetlacz bezprzewodowy"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Przesyłaj"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Połącz z urządzeniem"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Prześlij ekran na urządzenie"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Prześlij treści z ekranu na urządzenie"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"Szukam urządzeń…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Ustawienia"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Rozłącz"</string>
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Tryb jednej ręki"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatkowe przyciemnienie"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Urządzenia słuchowe"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatyczne kliknięcie"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Rozłączone"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Połączone"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktywne"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Możesz używać mikrofonu w aparacie słuchowym, aby dzwonić bez użycia rąk. Mikrofon zostanie przełączony tylko na czas połączenia."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Przełącz"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ustawienia"</string>
- <string name="user_switched" msgid="7249833311585228097">"Bieżący użytkownik: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Przełączam na użytkownika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Wylogowuję użytkownika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Właściciel"</string>
@@ -1969,7 +1967,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Aby odpiąć, poproś o wzór odblokowania"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Aby odpiąć, poproś o hasło"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Zainstalowany przez administratora.\nOtwórz ustawienia, aby wyświetlić przyznane uprawnienia"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Zaktualizowany przez administratora"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Usunięty przez administratora"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Oszczędzanie baterii uruchamia ciemny motyw oraz wyłącza lub ogranicza aktywność w tle, niektóre efekty wizualne, pewne funkcje oraz wybrane połączenia sieciowe."</string>
@@ -2277,6 +2276,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Przewijanie"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Wstrzymaj"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozycja"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Umieszczono pakiet <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> w zasobniku danych RESTRICTED"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"wysłano obraz"</string>
@@ -2484,6 +2495,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Służbowy 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Testowy"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Wspólny"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil służbowy"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Przestrzeń prywatna"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index f15ab35d54f0..cea3b73b2070 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo para uma mão"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Tela ainda mais escura"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Aparelhos auditivos"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clique automático"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Ativo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Você pode usar o microfone do aparelho auditivo para fazer ligações sem usar as mãos. A troca do microfone só será feita durante a ligação."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Trocar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configurações"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuário atual <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Mudando para <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Desconectando <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietário"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrão de desbloqueio antes de liberar"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir senha antes de liberar"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pelo administrador.\nAcesse as configurações para conferir as permissões concedidas"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Atualizado pelo seu administrador"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Excluído pelo seu administrador"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"A Economia de bateria ativa o tema escuro e limita ou desativa atividades em segundo plano, alguns efeitos visuais, recursos específicos e algumas conexões de rede."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Rolar"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausar"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posição"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> foi colocado no intervalo \"RESTRITO\""</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Teste"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Público"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e80b780c34cc..2951d482a490 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1714,7 +1714,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Visualização sem fios"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Transmitir"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Ligar ao dispositivo"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Transmitir ecrã para dispositivo"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Transmita o ecrã para o dispositivo"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"A pesquisar dispositivos…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Definições"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Desligar"</string>
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo para uma mão"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Mais escuro"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dispositivos auditivos"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clique automático"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desligado"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Ligado"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Ativo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Pode usar o microfone do aparelho auditivo para chamadas mãos-livres. Esta ação apenas muda o microfone durante a chamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Mudar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Definições"</string>
- <string name="user_switched" msgid="7249833311585228097">"<xliff:g id="NAME">%1$s</xliff:g> do utilizador atual."</string>
<string name="user_switching_message" msgid="1912993630661332336">"A mudar para <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"A terminar a sessão de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietário"</string>
@@ -1968,7 +1966,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrão de desbloqueio antes de soltar"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir palavra-passe antes de soltar"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pelo seu administrador.\nAceda às definições para ver as autorizações concedidas"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Atualizado pelo seu gestor"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Atualizado pelo seu administrador.\nAceda às definições para ver as autorizações concedidas"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminado pelo seu gestor"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"A Poupança de bateria ativa o tema escuro e limita ou desativa a atividade em segundo plano, alguns efeitos visuais, determinadas funcionalidades e algumas ligações de rede."</string>
@@ -2276,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Deslocar"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausar"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posição"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> foi colocado no contentor RESTRITO."</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string>
@@ -2483,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Teste"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Comum"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index f15ab35d54f0..cea3b73b2070 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo para uma mão"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Tela ainda mais escura"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Aparelhos auditivos"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clique automático"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Ativo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Você pode usar o microfone do aparelho auditivo para fazer ligações sem usar as mãos. A troca do microfone só será feita durante a ligação."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Trocar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configurações"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuário atual <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Mudando para <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Desconectando <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietário"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrão de desbloqueio antes de liberar"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir senha antes de liberar"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pelo administrador.\nAcesse as configurações para conferir as permissões concedidas"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Atualizado pelo seu administrador"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Excluído pelo seu administrador"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"A Economia de bateria ativa o tema escuro e limita ou desativa atividades em segundo plano, alguns efeitos visuais, recursos específicos e algumas conexões de rede."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Rolar"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausar"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posição"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> foi colocado no intervalo \"RESTRITO\""</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Teste"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Público"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index f02923b611d0..19158cc47ae2 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modul cu o mână"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Luminozitate redusă suplimentar"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Aparate auditive"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automat"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Deconectat"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectat"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Activ"</string>
@@ -1826,8 +1825,7 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Poți folosi microfonul aparatului auditiv pentru apelarea hands-free. Astfel, microfonul pornește numai în timpul apelului."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Schimbă"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Setări"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utilizator curent: <xliff:g id="NAME">%1$s</xliff:g>."</string>
- <string name="user_switching_message" msgid="1912993630661332336">"Se comută la <xliff:g id="NAME">%1$s</xliff:g>…"</string>
+ <string name="user_switching_message" msgid="1912993630661332336">"Se trece la <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Se deconectează utilizatorul <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietar"</string>
<string name="guest_name" msgid="8502103277839834324">"Invitat"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicită mai întâi modelul pentru deblocare"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicită parola înainte de a anula fixarea"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instalat de administrator.\nAccesează setările ca să vezi permisiunile acordate."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administrator"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Șters de administrator"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Economisirea bateriei activează tema întunecată și restricționează sau dezactivează activitatea în fundal, unele efecte vizuale, alte funcții și câteva conexiuni la rețea."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Derulează"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Întrerupe"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Poziție"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> a fost adăugat la grupul RESTRICȚIONATE"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"a trimis o imagine"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Serviciu 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Comun"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil de serviciu"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Spațiu privat"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clonă"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index e32ec412c4fd..136f76ae55ce 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим управления одной рукой"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Дополнительное уменьшение яркости"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слуховые аппараты"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Автонажатие"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Отключено"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Подключено"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Вы можете использовать микрофон слухового аппарата, когда разговариваете по телефону. Микрофон будет переключен только на время звонка."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Переключиться"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Настройки"</string>
- <string name="user_switched" msgid="7249833311585228097">"Выбран аккаунт пользователя <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Смена профиля на \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Выход из аккаунта <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Владелец"</string>
@@ -1969,7 +1967,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запрашивать графический ключ"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запрашивать пароль"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Установлено администратором.\nЧтобы посмотреть предоставленные разрешения, перейдите в настройки."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Обновлено администратором"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Удалено администратором"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"В режиме энергосбережения включается тёмная тема, ограничиваются или отключаются фоновые процессы, а также некоторые визуальные эффекты, часть функций и сетевых подключений."</string>
@@ -2277,6 +2276,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Прокрутить"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Приостановить"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Положение"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Приложение \"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>\" помещено в категорию с ограниченным доступом."</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"Отправлено изображение"</string>
@@ -2484,6 +2495,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Рабочий 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Тестовый"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Совместный"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Рабочий профиль"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Частное пространство"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клонированный"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 35251bcd2536..7a4de0eca678 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1713,7 +1713,7 @@
<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>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"තිරය උපාංගයට යොමු කරන්න"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"තිරය උපාංගයට විකාශය කරන්න"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"උපාංග සඳහා සොයමින්…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"සැකසීම්"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"විසන්ධි කරන්න"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"තනි අත් ප්‍රකාරය"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"තවත් අඳුරු"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ශ්‍රවණ උපාංග"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ස්වයං ක්ලික් කිරීම"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"විසන්ධි විය"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"සම්බන්ධිතයි"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"සක්‍රිය"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"දෑත් නොයොදන ඇමතුම් සඳහා ඔබට ඔබේ ශ්‍රවණාධාර මයික්‍රෆෝනය භාවිතා කළ හැක. මෙය ඇමතුම අතරතුර පමණක් ඔබේ මයික්‍රෆෝනය මාරු කරයි."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"මාරු කරන්න"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"සැකසීම්"</string>
- <string name="user_switched" msgid="7249833311585228097">"දැනට සිටින පරිශීලකයා <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> වෙත මාරු කරමින්…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> වරමින්…"</string>
<string name="owner_name" msgid="8713560351570795743">"හිමිකරු"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ගැලවීමට පෙර අගුළු අරින රටාව සඳහා අසන්න"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ගැලවීමට පෙර මුරපදය විමසන්න"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"ඔබේ පරිපාලකයා විසින් ස්ථාපන කරනු ලබයි.\nදෙන ලද අවසර බැලීමට සැකසීම් වෙත යන්න"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"ඔබගේ පරිපාලක මඟින් යාවත්කාලීන කර ඇත"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"ඔබගේ පරිපාලක මඟින් මකා දමා ඇත"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"හරි"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"බැටරි සුරැකුම අඳුරු තේමාව ක්‍රියාත්මක කර පසුබිම් ක්‍රියාකාරකම්, සමහර දෘශ්‍ය ප්‍රයෝග, යම් විශේෂාංග සහ සමහර ජාල සම්බන්ධතා සීමා හෝ ක්‍රියාවිරහිත කරයි."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"අනුචලනය කරන්න"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"විරාම කරන්න"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"ස්ථානය"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> අවහිර කළ බාල්දියට දමා ඇත"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"රූපයක් එව්වා"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"කාර්යාලය 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"පරීක්ෂණය"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"වාර්ගික"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"කාර්යාල පැතිකඩ"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"රහසිගත අවකාශය"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ක්ලෝන කරන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 391b0d76a104..c47ce658c1a8 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jednej ruky"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Mimoriadne stmavenie"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Načúvacie zariadenia"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatické kliknutie"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Odpojené"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Pripojené"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktívne"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Volať handsfree môžete pomocou mikrofónu načúvadla. Týmto iba prepnete mikrofón počas hovoru."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Prepnúť"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavenia"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuálny používateľ je <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prepína sa na účet <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Prebieha odhlásenie používateľa <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlastník"</string>
@@ -1969,7 +1967,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pred uvoľnením požiadať o bezpečnostný vzor"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pred odopnutím požiadať o heslo"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Nainštaloval správca.\nAk si chcete zobraziť udelené povolenia, prejdite do nastavení."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Aktualizoval správca"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Aktualizoval správca.\nAk si chcete zobraziť udelené povolenia, prejdite do nastavení."</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Odstránil správca"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Šetrič batérie zapne tmavý motív a obmedzí alebo vypne aktivitu na pozadí, niektoré vizuálne efekty, určité funkcie a niektoré pripojenia k sieti."</string>
@@ -2277,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Posúvať"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pozastaviť"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozícia"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Balík <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> bol vložený do kontajnera OBMEDZENÉ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"odoslal(a) obrázok"</string>
@@ -2484,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"3. pracovný"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Spoločné"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Pracovný profil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Súkromný priestor"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index c84fd1762e6f..07cb71c1b317 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enoročni način"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Zelo zatemnjen zaslon"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušni pripomočki"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Samodejni klik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Brez povezave"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mikrofon za slušni aparat lahko uporabljate za prostoročno klicanje. S tem preklopite mikrofon samo med klicem."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Preklopi"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavitve"</string>
- <string name="user_switched" msgid="7249833311585228097">"Trenutni uporabnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Preklapljanje na uporabnika <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljanje uporabnika <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="owner_name" msgid="8713560351570795743">"Lastnik"</string>
@@ -1969,7 +1967,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pred odpenjanjem vprašaj za vzorec za odklepanje"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pred odpenjanjem vprašaj za geslo"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Namestil skrbnik.\nV nastavitvah si oglejte odobrena dovoljenja."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Posodobil skrbnik"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Posodobil skrbnik.\nV nastavitvah si oglejte odobrena dovoljenja"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisal skrbnik"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"V redu"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Funkcija varčevanja z energijo baterije vklopi temno temo ter omeji ali izklopi dejavnost v ozadju, nekatere vizualne učinke, določene funkcije in nekatere omrežne povezave."</string>
@@ -2277,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Drsenje"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Začasna zaustavitev"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Položaj"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je bil dodan v segment OMEJENO"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslal(-a) sliko"</string>
@@ -2484,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Delo 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Preizkus"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Skupno"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Delovni profil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Zasebni prostor"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 25d51a358278..1b3feedaeccb 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -653,18 +653,18 @@
<string name="permlab_mediaLocation" msgid="7368098373378598066">"lexo vendndodhjet nga koleksioni yt i medias"</string>
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Lejon aplikacionin të lexojë vendndodhjet nga koleksioni yt i medias."</string>
<string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"të gjurmojë vështrimin e përafërt të syve"</string>
- <string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Lejon që aplikacioni të gjurmojë vështrimin e përafërt të syve"</string>
+ <string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Lejon që aplikacioni të gjurmojë vështrimin e përafërt të syve."</string>
<string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"të gjurmojë se ku po shikon ti"</string>
<string name="permdesc_eye_tracking_fine" msgid="5788889152304524730">"Lejon që aplikacioni të qaset në të dhënat e vështrimit të saktë të syve."</string>
<string name="permlab_face_tracking" msgid="2272048395128283324">"të gjurmojë fytyrën tënde"</string>
- <string name="permdesc_face_tracking" msgid="2622783922311211866">"Lejo që aplikacioni të qaset në të dhënat e gjurmimit të fytyrës."</string>
+ <string name="permdesc_face_tracking" msgid="2622783922311211866">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit të fytyrës."</string>
<string name="permlab_hand_tracking" msgid="6478233866595566940">"të gjurmojë duart e tua"</string>
<string name="permdesc_hand_tracking" msgid="8639715900104966456">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit të duarve."</string>
<string name="permlab_head_tracking" msgid="1309731456372087270">"të gjurmojë kokën tënde"</string>
- <string name="permdesc_head_tracking" msgid="231597390513699188">"Lejo që aplikacioni të qaset në të dhënat e gjurmimit të kokës."</string>
+ <string name="permdesc_head_tracking" msgid="231597390513699188">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit të kokës."</string>
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"të kuptojë mjedisin tënd"</string>
<string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit për mjedisin drejtpërdrejt rreth teje."</string>
- <string name="permlab_scene_understanding_fine" msgid="409126403264393251">"të kuptojë mjedisin tënd të drejtpërdrejtë me nivel të lartë të detajeve"</string>
+ <string name="permlab_scene_understanding_fine" msgid="409126403264393251">"të kuptojë mjedisin drejtpërdrejtë rreth teje me nivel të lartë të detajeve"</string>
<string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit për mjedisin drejtpërdrejt rreth teje me nivel shumë të lartë të detajeve."</string>
<string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"të qaset në të dhënat për XR kur nuk është në plan të parë"</string>
<string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Lejon që aplikacioni të qaset në të dhënat për XR kur nuk është në plan të parë."</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modaliteti i përdorimit me një dorë"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Shumë më i zbehtë"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Pajisjet e dëgjimit"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Klikimi automatik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Shkëputur"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Lidhur"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktive"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mund ta përdorësh mikrofonin e aparatit të dëgjimit për telefonata pa përdorur duart. Kjo vetëm ndërron mikrofonin gjatë telefonatës."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Ndërro"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Cilësimet"</string>
- <string name="user_switched" msgid="7249833311585228097">"Emri i përdoruesit aktual: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Po kalon në \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> po del…"</string>
<string name="owner_name" msgid="8713560351570795743">"Zotëruesi"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Kërko motivin e shkyçjes para heqjes së gozhdimit"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Kërko fjalëkalim para heqjes nga gozhdimi."</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Instaluar nga administratori.\nShko te cilësimet për të shikuar lejet e dhëna"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Përditësuar nga administratori"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Fshirë nga administratori"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Në rregull"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"\"Kursyesi i baterisë\" aktivizon \"Temën e errët\" dhe kufizon ose çaktivizon aktivitetin në sfond, disa efekte vizuale, veçori të caktuara dhe disa lidhje të rrjetit."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Lëviz"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Vendos në pauzë"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Pozicioni"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> është vendosur në grupin E KUFIZUAR"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"dërgoi një imazh"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Puna 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"I përbashkët"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profili i punës"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Hapësira private"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 77ceff214d60..24624f7e479f 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим једном руком"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Додатно затамни"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слушни апарати"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Аутоматски клик"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Веза је прекинута"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Повезано"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Можете да користите микрофон слушног апарата за хендсфри позивање. Тиме се микрофон мења само током позива."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Промени"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Подешавања"</string>
- <string name="user_switched" msgid="7249833311585228097">"Актуелни корисник <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Пребацивање на <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Одјављује се <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Власник"</string>
@@ -1968,7 +1966,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Тражи шаблон за откључавање пре откачињања"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тражи лозинку пре откачињања"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Инсталирао је администратор.\nИдите у подешавања да бисте видели одобрене дозволе"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ажурирао је администратор"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Избрисао је администратор"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Потврди"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Уштеда батерије укључује тамну тему и ограничава или искључује активности у позадини, неке визуелне ефекте, одређене функције и неке мрежне везе."</string>
@@ -2276,6 +2275,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Скролујте"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Паузирај"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Позиција"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> је додат у сегмент ОГРАНИЧЕНО"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"је послао/ла слику"</string>
@@ -2483,6 +2494,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Посао 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Тест"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Заједничко"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Пословни профил"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватан простор"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клонирано"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1ee2564ea4f6..c471f065b377 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -661,7 +661,7 @@
<string name="permlab_hand_tracking" msgid="6478233866595566940">"spåra dina händer"</string>
<string name="permdesc_hand_tracking" msgid="8639715900104966456">"Tillåter att appen får åtkomst till data om handspårning."</string>
<string name="permlab_head_tracking" msgid="1309731456372087270">"spåra ditt huvud"</string>
- <string name="permdesc_head_tracking" msgid="231597390513699188">"Tillåter att appen får åtkomst till data om huvudföljning."</string>
+ <string name="permdesc_head_tracking" msgid="231597390513699188">"Tillåter att appen får åtkomst till data om huvudspårning."</string>
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"förstå din omgivning"</string>
<string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Tillåter att appen får åtkomst till spårningsdata om omgivningen runtomkring dig."</string>
<string name="permlab_scene_understanding_fine" msgid="409126403264393251">"förstå din omgivning i detalj"</string>
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enhandsläge"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extradimmat"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hörhjälpmedel"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatiskt klick"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Frånkopplad"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Ansluten"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kan använda hörapparatens mikrofon för handsfree-samtal. Detta byter bara mikrofon under samtalet."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Byt"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Inställningar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Nuvarande användare: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Byter till <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Loggar ut <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="owner_name" msgid="8713560351570795743">"Ägare"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Be om upplåsningsmönster innan skärmen slutar fästas"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Be om lösenord innan skärmen slutar fästas"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Har installerats av administratören.\nÖppna inställningarna för att se behörigheter som beviljats"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Administratören uppdaterade paketet"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Har uppdaterats av administratören.\nÖppna inställningarna för att se behörigheter som beviljats"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Administratören raderade paketet"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"I batterisparläget aktiveras mörkt tema medan bakgrundsaktivitet, vissa visuella effekter och funktioner samt vissa nätverksanslutningar begränsas eller inaktiveras."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Scrolla"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pausa"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Position"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> har placerats i hinken RESTRICTED"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"har skickat en bild"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Arbete 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Allmän"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Jobbprofil"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat utrymme"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klona"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 36bf76dc7eb6..2a920b235335 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Hali ya kutumia kwa mkono mmoja"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Kipunguza mwangaza zaidi"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Vifaa vya kusaidia kusikia"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Kubofya kiotomatiki"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Haijaunganishwa"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Imeunganishwa"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Inatumika"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Unaweza kutumia maikrofoni ya visaidizi vyako vya kusikia ili kupiga simu bila kugusa. Hali hii hubadilisha maikrofoni yako tu wakati unapiga simu."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Badilisha"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Mipangilio"</string>
- <string name="user_switched" msgid="7249833311585228097">"Mtumiaji wa sasa <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Inaenda kwa <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Inamwondoa <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Mmiliki"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Omba mchoro wa kufungua kabla hujabandua"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Omba nenosiri kabla hujabandua"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Kimewekwa na msimamizi wako.\nNenda kwenye mipangilio ili uone ruhusa zilizotolewa"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Imesasishwa na msimamizi wako"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Imefutwa na msimamizi wako"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Sawa"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Kiokoa Betri huwasha Mandhari meusi na kudhibiti au kuzima shughuli za chinichini, baadhi ya madoido yanayoonekana, vipengele fulani na baadhi ya miunganisho ya mtandao."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Sogeza"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Sitisha"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Nafasi"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> kimewekwa katika kikundi KILICHODHIBITIWA"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"alituma picha"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Wa 3 wa Kazini"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Jaribio"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Unaoshirikiwa"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Wasifu wa kazini"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Sehemu ya faragha"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nakala"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3fa382783be1..1fade556f421 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ஒற்றைக் கைப் பயன்முறை"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"மிகக் குறைவான வெளிச்சம்"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"செவித்துணைக் கருவிகள்"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ஆட்டோ கிளிக்"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"இணைப்புநீக்கப்பட்டது"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"இணைக்கப்பட்டுள்ளது"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"செயலில் உள்ளது"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"கைகளைப் பயன்படுத்தாமல் அழைக்க உங்கள் செவித்துணைக் கருவியின் மைக்ரோஃபோனைப் பயன்படுத்தலாம். அழைப்பின்போது உங்கள் மைக்கை மட்டுமே இது மாற்றுகிறது."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"மாற்று"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"அமைப்புகள்"</string>
- <string name="user_switched" msgid="7249833311585228097">"நடப்பு பயனர் <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>க்கு மாறுகிறது…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> வெளியேறுகிறார்…"</string>
<string name="owner_name" msgid="8713560351570795743">"உரிமையாளர்"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"அகற்றும் முன் அன்லாக் பேட்டர்னைக் கேள்"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"அகற்றும் முன் கடவுச்சொல்லைக் கேள்"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"உங்கள் நிர்வாகி நிறுவியுள்ளார்.\nவழங்கப்பட்டுள்ள அனுமதிகளை பார்க்க அமைப்புகளுக்குச் செல்லவும்"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"உங்கள் நிர்வாகி புதுப்பித்துள்ளார்"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"உங்கள் நிர்வாகி நீக்கியுள்ளார்"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"சரி"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"பேட்டரி சேமிப்பான் அம்சம் டார்க் தீமை இயக்குவதோடு பின்னணிச் செயல்பாடு, சில விஷுவல் எஃபக்ட்கள், குறிப்பிட்ட அம்சங்கள், சில நெட்வொர்க் இணைப்புகள் ஆகியவற்றைக் கட்டுப்படுத்தும் அல்லது முடக்கும்."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"நகர்த்தும்"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"இடைநிறுத்து"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"நிலை"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> என்பதை வரம்பிடப்பட்ட பக்கெட்திற்குள் சேர்க்கப்பட்டது"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"படம் அனுப்பப்பட்டது"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"பணி 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"பரிசோதனை"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"பொது"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"பணிக் கணக்கு"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ரகசிய இடம்"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"குளோன்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 027161e986fc..4f6eaf3b2307 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"వన్-హ్యాండెడ్ మోడ్"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ఎక్స్‌ట్రా డిమ్"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"వినికిడి పరికరాలు"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ఆటో-క్లిక్"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"డిస్‌కనెక్ట్ అయింది"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"కనెక్ట్ చేయబడింది"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"యాక్టివ్"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"హ్యాండ్స్-ఫ్రీ కాలింగ్ కోసం మీరు మీ వినికిడి పరికరాన్ని ఉపయోగించవచ్చు. ఇది కాల్ సమయంలో మాత్రమే మీ మైక్‌ను మారుస్తుంది."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"మారండి"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"సెట్టింగ్‌లు"</string>
- <string name="user_switched" msgid="7249833311585228097">"ప్రస్తుత వినియోగదారు <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> యూజర్‌కు స్విచ్ అవుతోంది…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ని లాగ్ అవుట్ చేస్తోంది…"</string>
<string name="owner_name" msgid="8713560351570795743">"ఓనర్"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"అన్‌పిన్ చేయడానికి ముందు అన్‌లాక్ ఆకృతి కోసం అడుగు"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"అన్‌పిన్ చేయడానికి ముందు పాస్‌వర్డ్ కోసం అడుగు"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"మీ అడ్మిన్ ఇన్‌స్టాల్ చేశారు.\nసెట్టింగ్‌లకు వెళ్లి, మంజూరు చేసిన అనుమతులు చూడండి"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"మీ నిర్వాహకులు అప్‌డేట్ చేశారు"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"మీ అడ్మిన్ అప్‌డేట్ చేశారు.\nసెట్టింగ్‌లకు వెళ్లి, మంజూరు చేసిన అనుమతులను చూడండి"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"మీ నిర్వాహకులు తొలగించారు"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"సరే"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"బ్యాటరీ సేవర్ ముదురు రంగు రూపాన్ని ఆన్ చేసి, బ్యాక్‌గ్రౌండ్ యాక్టివిటీ, కొన్ని విజువల్ ఎఫెక్ట్‌లు, నిర్దిష్ట ఫీచర్‌లు, ఇంకా కొన్ని నెట్‌వర్క్ కనెక్షన్‌లను పరిమితం చేస్తుంది లేదా ఆఫ్ చేస్తుంది."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"స్క్రోల్ చేయండి"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"పాజ్ చేయండి"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"స్థానం"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> పరిమితం చేయబడిన బకెట్‌లో ఉంచబడింది"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ఇమేజ్‌ను పంపారు"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"ఆఫీస్ 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"పరీక్ష"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"కమ్యూనల్"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"వర్క్ ప్రొఫైల్"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"ప్రైవేట్ స్పేస్"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"క్లోన్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index f82adc6a4f64..b67f3828ab72 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"โหมดมือเดียว"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"หรี่แสงเพิ่มเติม"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"เครื่องช่วยฟัง"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"การคลิกอัตโนมัติ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"เลิกเชื่อมต่อแล้ว"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"เชื่อมต่อแล้ว"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ใช้งานอยู่"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"คุณใช้ไมโครโฟนของเครื่องช่วยฟังเพื่อโทรแบบแฮนด์ฟรีได้ การดำเนินการนี้เพียงแค่เปลี่ยนไมโครโฟนระหว่างการโทรเท่านั้น"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"เปลี่ยน"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"การตั้งค่า"</string>
- <string name="user_switched" msgid="7249833311585228097">"ผู้ใช้ปัจจุบัน <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"กำลังเปลี่ยนเป็น<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"กำลังออกจากระบบ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"เจ้าของ"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ขอรูปแบบการปลดล็อกก่อนเลิกปักหมุด"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ขอรหัสผ่านก่อนเลิกปักหมุด"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"ติดตั้งโดยผู้ดูแลระบบของคุณ\nไปที่การตั้งค่าเพื่อดูสิทธิ์ที่ได้รับอนุญาต"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"อัปเดตโดยผู้ดูแลระบบ"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"อัปเดตโดยผู้ดูแลระบบของคุณ\nไปที่การตั้งค่าเพื่อดูสิทธิ์ที่ได้รับอนุญาต"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"ลบโดยผู้ดูแลระบบ"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ตกลง"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"โหมดประหยัดแบตเตอรี่จะเปิดธีมมืดและจำกัดหรือปิดกิจกรรมในเบื้องหลัง เอฟเฟกต์ภาพบางอย่าง ฟีเจอร์บางส่วน และการเชื่อมต่อบางเครือข่าย"</string>
@@ -2275,13 +2273,25 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"เลื่อน"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"หยุดชั่วคราว"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"วางตำแหน่ง"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"ใส่ <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ในที่เก็บข้อมูลที่ถูกจำกัดแล้ว"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ส่งรูปภาพ"</string>
<string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"การสนทนา"</string>
<string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"บทสนทนากลุ่ม"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
- <string name="resolver_personal_tab" msgid="2051260504014442073">"ส่วนบุคคล"</string>
+ <string name="resolver_personal_tab" msgid="2051260504014442073">"ส่วนตัว"</string>
<string name="resolver_work_tab" msgid="2690019516263167035">"งาน"</string>
<string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"มุมมองส่วนตัว"</string>
<string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"ดูงาน"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"งาน 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ทดสอบ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"ส่วนกลาง"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"โปรไฟล์งาน"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"พื้นที่ส่วนตัว"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"โคลน"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 328997d33382..3c380884ceea 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Hand mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Mga hearing device"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nadiskonekta"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Nakakonekta"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktibo"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puwede mong gamitin ang mikropono ng iyong hearing aid para sa hands-free na pagtawag. Inililipat lang nito ang iyong mikropono habang nasa tawag ka."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Lumipat"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Mga Setting"</string>
- <string name="user_switched" msgid="7249833311585228097">"Kasalukuyang user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Lumilipat kay <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Nila-log out si <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="owner_name" msgid="8713560351570795743">"May-ari"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Humingi ng pattern sa pag-unlock bago mag-unpin"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Humingi ng password bago mag-unpin"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Na-install ng iyong admin.\nPumunta sa mga setting para makita ang mga ibinigay na pahintulot"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Na-update ng iyong admin"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"Na-update ng iyong admin.\nPumunta sa mga setting para makita ang mga ibinigay na pahintulot"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Na-delete ng iyong admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ino-on ng Pantipid ng Baterya ang Madilim na tema at nililimitahan o ino-off nito ang aktibidad sa background, ilang visual effect, ilang partikular na feature, at ilang koneksyon sa network."</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Mag-scroll"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"I-pause"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Posisyon"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Inilagay ang <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> sa PINAGHIHIGPITANG bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"nagpadala ng larawan"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Trabaho 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profile sa trabaho"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Pribadong space"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5106e1a6545d..dfaa061ba093 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Tek El modu"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra loş"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"İşitme cihazları"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Otomatik tıklama"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Bağlı değil"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Bağlı"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Etkin"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Eller serbest modunda arama yapmak için işitme cihazı mikrofonunu kullanabilirsiniz. Bu işlem yalnızca görüşme sırasında mikrofonunuzu değiştirir."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Geçiş yap"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ayarlar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Geçerli kullanıcı: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> adlı kullanıcıya geçiliyor…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> hesabından çıkış yapılıyor…"</string>
<string name="owner_name" msgid="8713560351570795743">"Sahip"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Sabitlemeyi kaldırmadan önce kilit açma desenini sor"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Sabitlemeyi kaldırmadan önce şifre sor"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Yöneticiniz tarafından yüklendi.\nVerilen izinleri görüntülemek için ayarlara gidin"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Yöneticiniz tarafından güncellendi"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Yöneticiniz tarafından silindi"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Tamam"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Pil Tasarrufu, Koyu temayı açıp arka plan etkinliğini, bazı görsel efektleri, belirli özellikleri ve bazı ağ bağlantılarını sınırlandırır veya kapatır."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Kaydırma"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Duraklatma"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Konum"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> KISITLANMIŞ gruba yerleştirildi"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"bir resim gönderildi"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"İş 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Paylaşılan"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"İş profili"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Özel alan"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 203cdfac5c1d..34dda976f1bc 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1805,8 +1805,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим керування однією рукою"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Додаткове зменшення яскравості"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слухові апарати"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматичне натискання"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Від’єднано"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Під’єднано"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Активний"</string>
@@ -1827,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Ви можете використовувати мікрофон слухового апарата для голосового керування викликами. Мікрофон перемикатиметься лише на час дзвінка."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Перемкнути"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Налаштування"</string>
- <string name="user_switched" msgid="7249833311585228097">"Поточний користувач: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Перехід в обліковий запис \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Вихід з облікового запису користувача <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Власник"</string>
@@ -1969,7 +1967,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запитувати ключ розблокування перед відкріпленням"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запитувати пароль перед відкріпленням"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Установлено адміністратором.\nПерейдіть у налаштування, щоб переглянути надані дозволи."</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Оновлено адміністратором"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Видалено адміністратором"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"У режимі енергозбереження вмикається темна тема й обмежуються чи вимикаються дії у фоновому режимі, а також деякі візуальні ефекти, функції та з’єднання з мережами."</string>
@@ -2277,6 +2276,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Прокрутити"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Призупинити"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Змінити позицію"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет \"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>\" додано в сегмент з обмеженнями"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"надіслано зображення"</string>
@@ -2484,6 +2495,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Робочий профіль 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Тестовий профіль"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Спільний профіль"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Робочий профіль"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватний простір"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Копія профілю"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 376efd1f0bd6..31b5999b6f15 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ایک ہاتھ کی وضع"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"اضافی مدھم"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"سماعتی آلات"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"خودکار کلک"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"غیر منسلک ہے"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"منسلک ہے"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"فعال"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"آپ ہینڈز فری کالنگ کے لیے اپنا سماعتی آلہ مائیکروفون استعمال کر سکتے ہیں۔ یہ صرف کال کے دوران آپ کا مائیک سوئچ کرتا ہے۔"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"سوئچ کریں"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ترتیبات"</string>
- <string name="user_switched" msgid="7249833311585228097">"موجودہ صارف <xliff:g id="NAME">%1$s</xliff:g>۔"</string>
<string name="user_switching_message" msgid="1912993630661332336">"‫<xliff:g id="NAME">%1$s</xliff:g> پر سوئچ کیا جا رہا ہے…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> لاگ آؤٹ ہو رہا ہے…"</string>
<string name="owner_name" msgid="8713560351570795743">"مالک"</string>
@@ -1967,7 +1965,7 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"پن ہٹانے سے پہلے غیر مقفل کرنے کا پیٹرن طلب کریں"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"پن ہٹانے سے پہلے پاس ورڈ طلب کریں"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"آپ کے منتظم نے انسٹال کیا ہے۔\nدی گئی اجازتیں دیکھنے کیلئے ترتیبات پر جائیں"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"آپ کے منتظم کے ذریعے اپ ڈیٹ کیا گیا"</string>
+ <string name="package_updated_device_owner" msgid="7770195449213776218">"آپ کے منتظم نے اپ ڈیٹ کیا ہے۔\nدی گئی اجازتیں دیکھنے کیلئے ترتیبات پر جائیں"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"آپ کے منتظم کے ذریعے حذف کیا گیا"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ٹھیک ہے"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"بیٹری سیور گہری تھیم کو آن کرتی ہے اور پس منظر کی سرگرمی، کچھ بصری اثرات، مخصوص خصوصیات اور کچھ نیٹ ورک کنکشنز کو محدود یا آف کرتی ہے۔"</string>
@@ -2275,6 +2273,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"اسکرول کریں"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"موقوف کریں"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"پوزیشن"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> کو پابند کردہ بکٹ میں رکھ دیا گیا ہے"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ایک تصویر بھیجی"</string>
@@ -2482,6 +2492,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"تیسری دفتری پروفائل"</string>
<string name="profile_label_test" msgid="9168641926186071947">"ٹیسٹ"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"کمیونل"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"دفتری پروفائل"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"پرائیویٹ اسپیس"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"کلون"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a1b9e5d0a754..35c35ac6d90b 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Ixcham rejim"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Juda xira"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Eshitish qurilmalari"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Avtoklik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Uzildi"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Ulandi"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Faol"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Garniturali chaqiruv uchun eshitish moslamasi mikrofonidan foydalanish mumkin. Bu faqat chaqiruv paytida mikrofonni almashtiradi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Almashtirish"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Sozlamalar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Joriy foydalanuvchi <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Bunga almashilmoqda: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> hisobidan chiqilmoqda…"</string>
<string name="owner_name" msgid="8713560351570795743">"Egasi"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Yechishdan oldin grafik kalit so‘ralsin"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Bo‘shatishdan oldin parol so‘ralsin"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Administrator oʻrnatgan.\nBerilgan ruxsatlarni koʻrish uchun sozlamalarni oching"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Administrator tomonidan yangilangan"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Administrator tomonidan o‘chirilgan"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Quvvat tejash funksiyasi Tungi mavzuni va cheklovlarni yoqadi hamda fondagi harakatlar, vizual effektlar, ayrim funksiyalar va tarmoq aloqalari kabi boshqa funksiyalarni faolsizlantiradi yoki cheklaydi."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Aylantirish"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Pauza"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Joylashuvi"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> cheklangan turkumga joylandi"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"rasm yuborildi"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Ish 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Umumiy"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Ish profili"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Maxfiy makon"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nusxalash"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 473568de3e31..626625f85d40 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Chế độ một tay"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Siêu tối"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Thiết bị trợ thính"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Tự động nhấp"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Đã ngắt kết nối"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Đã kết nối"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Đang hoạt động"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Bạn có thể sử dụng micrô của thiết bị trợ thính để gọi điện mà không cần dùng tay. Thao tác này chỉ chuyển micrô của bạn trong cuộc gọi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Chuyển"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Cài đặt"</string>
- <string name="user_switched" msgid="7249833311585228097">"Người dùng hiện tại <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Đang chuyển sang <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Đang đăng xuất <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Chủ sở hữu"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Hỏi hình mở khóa trước khi bỏ ghim"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Hỏi mật khẩu trước khi bỏ ghim"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Do quản trị viên của bạn cài đặt.\nChuyển đến phần cài đặt để xem các quyền được cấp"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Do quản trị viên của bạn cập nhật"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Do quản trị viên của bạn xóa"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Trình tiết kiệm pin sẽ bật Giao diện tối, đồng thời hạn chế hoặc tắt hoạt động chạy trong nền, một số hiệu ứng hình ảnh, các tính năng nhất định và một số đường kết nối mạng."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Cuộn"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Tạm dừng"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Vị trí"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Đã đưa <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> vào bộ chứa BỊ HẠN CHẾ"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"đã gửi hình ảnh"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Công việc 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Kiểm thử"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Dùng chung"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Hồ sơ công việc"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Không gian riêng tư"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nhân bản"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index f91f457b85cc..921a464c434e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"单手模式"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"极暗"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"助听装置"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"自动点击"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"已断开连接"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"已连接"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"活跃"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"您可以使用助听器麦克风进行免提通话。此操作只会切换通话期间的麦克风。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切换"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"设置"</string>
- <string name="user_switched" msgid="7249833311585228097">"当前用户是<xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"正在切换为<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"正在将<xliff:g id="NAME">%1$s</xliff:g>退出账号…"</string>
<string name="owner_name" msgid="8713560351570795743">"机主"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定前要求绘制解锁图案"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消时要求输入密码"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"由您的管理员安装。\n前往设置可查看已授予的权限"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"已由您的管理员更新"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"已由您的管理员删除"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"确定"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"在省电模式下,系统会启用深色主题,并限制或关闭后台活动、某些视觉效果、特定功能和部分网络连接。"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"滚动"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"暂停"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"位置"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 已被放入受限存储分区"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"发送了一张图片"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"测试"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"共用"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作资料"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"私密空间"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"克隆"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 9bcb1730d63f..8681d036948a 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"單手模式"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"超暗"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"助聽器"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"自動點擊"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"已中斷連線"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"已連線"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"運作中"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"你可使用助聽器麥克風進行免提通話。此操作只會切換通話期間的麥克風。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切換"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"設定"</string>
- <string name="user_switched" msgid="7249833311585228097">"目前的使用者是<xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"正在切換至<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"正在登出 <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"擁有者"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須提供解鎖圖案"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消固定時必須輸入密碼"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"已由你的管理員安裝。\n請前往設定查看已授予的權限"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"已由你的管理員更新"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"已由你的管理員刪除"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"好"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"「慳電模式」會開啟深色主題背景,並限制或關閉背景活動、部分視覺效果、特定功能和部分網絡連線。"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"捲動"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"暫停"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"位置"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 已納入受限制的儲存區"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"已傳送圖片"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"測試"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"共用"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作設定檔"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"私人空間"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9f8fd6713097..21e9113bc9a0 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"單手模式"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"超暗"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"助聽器"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"自動點選"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"連線中斷"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"已連線"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"運作中"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"免持通話時,可以使用助聽器麥克風。麥克風只會在通話期間切換。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切換"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"設定"</string>
- <string name="user_switched" msgid="7249833311585228097">"目前的使用者是 <xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"正在切換至<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"正在將<xliff:g id="NAME">%1$s</xliff:g>登出帳戶…"</string>
<string name="owner_name" msgid="8713560351570795743">"擁有者"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須畫出解鎖圖案"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消固定時必須輸入密碼"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"這是管理員安裝的套件。\n你可以前往設定查看授予的權限"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"已由你的管理員更新"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"已由你的管理員刪除"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"確定"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"省電模式會開啟深色主題,並限制或關閉背景活動、某些視覺效果、特定功能和部分網路連線。"</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"捲動"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"暫停"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"位置"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"已將「<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>」移入受限制的值區"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"傳送了一張圖片"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"測試"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"通用"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作資料夾"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"私人空間"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a0df4a1cbc0b..695f3198c90b 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Imodi yesandla esisodwa"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ukufiphaza okwengeziwe"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Amadivayizi okuzwa"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Chofoza ngokuzenzekelayo"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Inqamukile"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Ixhunyiwe"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Kuyasebenza"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Ungasebenzisa imakrofoni yakho yomshini wendlebe ekufoneni ngehands-free. Lokhu kushintsha kuphela imakrofoni yakho ngesikhathi sekholi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Shintsha"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Amasethingi"</string>
- <string name="user_switched" msgid="7249833311585228097">"Umsebenzisi wamanje <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Ishintshela ku-<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Iyaphuma <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Umnikazi"</string>
@@ -1967,7 +1965,8 @@
<string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Cela iphethini yokuvula ngaphambi kokususa ukuphina"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Cela iphasiwedi ngaphambi kokususa ukuphina"</string>
<string name="package_installed_device_owner" msgid="8684974629306529138">"Kufakwe ngumphathi wakho.\nIya kumasethingi ukuze ubuke izimvume ezinikeziwe"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Kubuyekezwe umlawuli wakho"</string>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Kususwe umlawuli wakho"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"KULUNGILE"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Isilondolozi Sebhethri sivula ingqikithi emnyama futhi sibeke umkhawulo noma sivale umsebenzi ongemuva, imiphumela ethile yokubuka, izici ezithile, nokuxhumeka okuthile kwenethiwekhi."</string>
@@ -2275,6 +2274,18 @@
<string name="accessibility_autoclick_scroll" msgid="3499385943728726933">"Skrola"</string>
<string name="accessibility_autoclick_pause" msgid="3272200156172573568">"Misa"</string>
<string name="accessibility_autoclick_position" msgid="2933660969907663545">"Indawo"</string>
+ <!-- no translation found for accessibility_autoclick_scroll_up (2044948780797117443) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_down (3733401063292018116) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_left (8564421367992824198) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_right (8932417330753984265) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_exit (3788610039146769696) -->
+ <skip />
+ <!-- no translation found for accessibility_autoclick_scroll_panel_title (7120598166296447036) -->
+ <skip />
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"I-<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ifakwe kubhakede LOKUKHAWULELWE"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"uthumele isithombe"</string>
@@ -2482,6 +2493,8 @@
<string name="profile_label_work_3" msgid="4834572253956798917">"Umsebenzi 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Hlola"</string>
<string name="profile_label_communal" msgid="8743921499944800427">"Okomphakathi"</string>
+ <!-- no translation found for profile_label_supervising (5649312778545745371) -->
+ <skip />
<string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Iphrofayela yomsebenzi"</string>
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Indawo engasese"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Yenza i-Clone"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1cb38bed1388..43486f85f5d6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2812,11 +2812,29 @@
<integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
<!-- Limit of how long the device can remain unlocked due to attention checking. -->
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
+
+ <!-- Enables or disables the 'prevent screen timeout' feature, where when a user manually
+ undims the screen, the feature acquires a wakelock to prevent screen timeout.
+ false = disabled, true = enabled. Disabled by default. -->
+ <bool name="config_defaultPreventScreenTimeoutEnabled">false</bool>
+ <!-- Default value (in milliseconds) to prevent the screen timeout after user undims it
+ manually between screen dims, a sign the user is interacting with the device. -->
+ <integer name="config_defaultPreventScreenTimeoutForMillis">300000</integer> <!-- 5 minutes. -->
+ <!-- Default max duration (in milliseconds) of the time between undims to still consider them
+ consecutive. -->
+ <integer name="config_defaultMaxDurationBetweenUndimsMillis">600000</integer> <!-- 10 min. -->
+ <!-- Default number of user undims required to trigger preventing screen sleep. -->
+ <integer name="config_defaultUndimsRequired">2</integer>
+
<!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. -->
<bool name="config_dreamsOnlyEnabledForDockUser">false</bool>
<!-- Whether dreams are disabled when ambient mode is suppressed. -->
<bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
+ <!-- The default for the setting that controls when to auto-start hub mode.
+ 0 means "never" -->
+ <integer name="config_whenToStartHubModeDefault">0</integer>
+
<!-- The duration in milliseconds of the dream opening animation. -->
<integer name="config_dreamOpenAnimationDuration">250</integer>
<!-- The duration in milliseconds of the dream closing animation. -->
@@ -3072,6 +3090,43 @@
{@link MotionEvent#ACTION_SCROLL} event. -->
<dimen name="config_scrollFactor">64dp</dimen>
+ <!-- Duration in milliseconds of the pressed state in child components. -->
+ <integer name="config_pressedStateDurationMillis">64</integer>
+
+ <!-- Duration in milliseconds we will wait to see if a touch event is a tap or a scroll.
+ If the user does not move within this interval, it is considered to be a tap. -->
+ <integer name="config_tapTimeoutMillis">100</integer>
+
+ <!-- Duration in milliseconds we will wait to see if a touch event is a jump tap.
+ If the user does not move within this interval, it is considered to be a tap. -->
+ <integer name="config_jumpTapTimeoutMillis">500</integer>
+
+ <!-- Duration in milliseconds between the first tap's up event and the second tap's down
+ event for an interaction to be considered a double-tap. -->
+ <integer name="config_doubleTapTimeoutMillis">300</integer>
+
+ <!-- Minimum duration in milliseconds between the first tap's up event and the second tap's
+ down event for an interaction to be considered a double-tap. -->
+ <integer name="config_doubleTapMinTimeMillis">40</integer>
+
+ <!-- Maximum duration in milliseconds between a touch pad touch and release for a given touch
+ to be considered a tap (click) as opposed to a hover movement gesture. -->
+ <integer name="config_hoverTapTimeoutMillis">150</integer>
+
+ <!-- The amount of time in milliseconds that the zoom controls should be displayed on the
+ screen. -->
+ <integer name="config_zoomControlsTimeoutMillis">3000</integer>
+
+ <!-- Default duration in milliseconds for {@link ActionMode#hide(long)}. -->
+ <integer name="config_defaultActionModeHideDurationMillis">2000</integer>
+
+ <!-- Maximum distance in pixels that a touch pad touch can move before being released
+ for it to be considered a tap (click) as opposed to a hover movement gesture. -->
+ <dimen name="config_hoverTapSlop">20px</dimen>
+
+ <!-- The amount of friction applied to scrolls and flings. -->
+ <item name="config_scrollFriction" format="float" type="dimen">0.015</item>
+
<!-- Maximum number of grid columns permitted in the ResolverActivity
used for picking activities to handle an intent. -->
<integer name="config_maxResolverActivityColumns">3</integer>
@@ -4133,6 +4188,11 @@
<!-- Whether device supports double tap to wake -->
<bool name="config_supportDoubleTapWake">false</bool>
+ <!-- Whether device supports double tap to sleep. This will allow the user to enable/disable
+ double tap gestures in non-action areas in the lock screen and launcher workspace to go to
+ sleep. -->
+ <bool name="config_supportDoubleTapSleep">false</bool>
+
<!-- The RadioAccessFamilies supported by the device.
Empty is viewed as "all". Only used on devices which
don't support RIL_REQUEST_GET_RADIO_CAPABILITY
@@ -4422,6 +4482,10 @@
etc. dialogs. -->
<string translatable="false" name="config_appsNotReportingCrashes"></string>
+ <!-- Specifies the delay in milliseconds for JobScheduler to postpone the running
+ of regular jobs when coming out of doze -->
+ <integer name="config_jobSchedulerBackgroundJobsDelay">3000</integer>
+
<!-- Inactivity threshold (in milliseconds) used in JobScheduler. JobScheduler will consider
the device to be "idle" after being inactive for this long. -->
<integer name="config_jobSchedulerInactivityIdleThreshold">1860000</integer>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 59ed25a1865f..ef6b9188532e 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -80,11 +80,6 @@
<bool name="auto_data_switch_ping_test_before_switch">true</bool>
<java-symbol type="bool" name="auto_data_switch_ping_test_before_switch" />
- <!-- TODO: remove after V -->
- <!-- Boolean indicating whether allow to use a roaming nonDDS if user enabled its roaming. -->
- <bool name="auto_data_switch_allow_roaming">true</bool>
- <java-symbol type="bool" name="auto_data_switch_allow_roaming" />
-
<!-- Define the tolerated gap of score for auto data switch decision, larger than which the
device will switch to the SIM with higher score. The score is used in conjunction with the
score table defined in
@@ -524,4 +519,9 @@
capabilities. -->
<string-array name="config_unsupported_network_capabilities" translatable="false"></string-array>
<java-symbol type="array" name="config_unsupported_network_capabilities" />
+
+ <!-- Whether to enable APDU sender optimization i.e. a logical channel is opened and
+ kept open for multiple APDU commands within one session.-->
+ <bool name="euicc_optimize_apdu_sender">false</bool>
+ <java-symbol type="bool" name="euicc_optimize_apdu_sender" />
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a1961aedf6b7..b5ed28f82dda 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -277,12 +277,29 @@
<!-- The margin on the end of the top-line content views (accommodates the expander) -->
<dimen name="notification_heading_margin_end">56dp</dimen>
+ <!-- The vertical spacing for the smart reply/smart action container.
+ Note that the button background itself also has an inset of 6dp (making the height of the
+ tappable area 48dp total, 32dp for the visible button plus 6dp top and bottom), so the visible
+ space between the button and the other content is going to be 16dp. -->
+ <dimen name="notification_2025_smart_reply_container_margin">10dp</dimen>
+
<!-- The total height of the notification action list -->
<dimen name="notification_action_list_height">60dp</dimen>
+ <!-- The total height of the notification action list (2025 redesign version) -->
+ <dimen name="notification_2025_action_list_height">48dp</dimen>
+
<!-- The margin of the notification action list at the top -->
<dimen name="notification_action_list_margin_top">0dp</dimen>
+ <!-- The margin of the notification action list at the bottom in the 2025 redesign -->
+ <dimen name="notification_2025_action_list_margin_bottom">6dp</dimen>
+
+ <!-- The minimum height of the notification action container, to act as a bottom padding for the
+ notification when there are no actions. This should always be equal to
+ notification_2025_margin - notification_2025_action_list_margin_bottom. -->
+ <dimen name="notification_2025_action_list_min_height">10dp</dimen>
+
<!-- The overall height of the emphasized notification action -->
<dimen name="notification_action_emphasized_height">48dp</dimen>
@@ -304,6 +321,9 @@
<!-- The size of icons for visual actions in the notification_material_action_list -->
<dimen name="notification_actions_icon_size">56dp</dimen>
+ <!-- The size of icon actions in notification_material_action_list (2025 redesign version) -->
+ <dimen name="notification_2025_actions_icon_size">48dp</dimen>
+
<!-- The size of icons for visual actions in the notification_material_action_list -->
<dimen name="notification_actions_icon_drawable_size">20dp</dimen>
@@ -390,6 +410,16 @@
<!-- The absolute size of the notification expand icon. -->
<dimen name="notification_header_expand_icon_size">56dp</dimen>
+ <!-- Margin to allow space for the expand button when showing the right icon in expanded -->
+ <!-- notifications. This is equal to notification_2025_expand_button_pill_width -->
+ <!-- + notification_2025_margin (end padding for expand button) -->
+ <!-- + notification_2025_expand_button_right_icon_spacing (space between pill and icon) -->
+ <dimen name="notification_2025_right_icon_expanded_margin_end">52dp</dimen>
+
+ <!-- The large icon has a smaller vertical margin than most other notification content, to -->
+ <!-- allow it to grow up to 48dp. -->
+ <dimen name="notification_2025_right_icon_vertical_margin">12dp</dimen>
+
<!-- the height of the expand button pill -->
<dimen name="notification_expand_button_pill_height">24dp</dimen>
@@ -411,15 +441,24 @@
<!-- the padding of the expand icon in the notification header -->
<dimen name="notification_2025_expand_button_horizontal_icon_padding">6dp</dimen>
- <!-- a smaller padding for the end of the expand button, for use when showing the number -->
+ <!-- smaller padding for the end of the expand icon, for use when showing the number -->
<dimen name="notification_2025_expand_button_reduced_end_padding">4dp</dimen>
+ <!-- the space needed between the expander pill and the large icon when visible -->
+ <dimen name="notification_2025_expand_button_right_icon_spacing">8dp</dimen>
+
<!-- the size of the notification close button -->
<dimen name="notification_close_button_size">16dp</dimen>
<!-- Margin for all notification content -->
<dimen name="notification_2025_margin">16dp</dimen>
+ <!-- A smaller version of the margin to be used when we need more space for the content -->
+ <dimen name="notification_2025_reduced_margin">12dp</dimen>
+
+ <!-- The difference between the usual margin and the reduced margin -->
+ <dimen name="notification_2025_additional_margin">4dp</dimen>
+
<!-- Vertical margin for the headerless notification content, when content has 1 line -->
<!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
<dimen name="notification_headerless_margin_oneline">16dp</dimen>
@@ -438,7 +477,7 @@
<dimen name="notification_collapsed_height_with_summarization">156dp</dimen>
<!-- Max height of a collapsed (headerless) notification with one or two lines -->
- <!-- 16 * 2 (margins) + 48 (min content height) = 72 (notification) -->
+ <!-- 14 * 2 (reduced margins) + 48 (max collapsed content height) = 72 (notification) -->
<dimen name="notification_2025_min_height">72dp</dimen>
<!-- Height of a headerless notification with one line -->
@@ -729,6 +768,9 @@
<!-- The accessibility autoclick panel divider height -->
<dimen name="accessibility_autoclick_type_panel_divider_height">24dp</dimen>
+ <!-- The accessibility autoclick scroll panel button width and height -->
+ <dimen name="accessibility_autoclick_scroll_panel_button_size">36dp</dimen>
+
<!-- Margin around the various security views -->
<dimen name="keyguard_muliuser_selector_margin">8dp</dimen>
@@ -893,6 +935,8 @@
<dimen name="notification_right_icon_size">48dp</dimen>
<!-- The margin between the right icon and the content. -->
<dimen name="notification_right_icon_content_margin">12dp</dimen>
+ <!-- The margin between the right icon and the content. (2025 redesign version) -->
+ <dimen name="notification_2025_right_icon_content_margin">16dp</dimen>
<!-- The top and bottom margin of the right icon in the normal notification states -->
<dimen name="notification_right_icon_headerless_margin">20dp</dimen>
<!-- The top margin of the right icon in the "big" notification states -->
@@ -1211,4 +1255,7 @@
<!-- The maximum width for a context menu icon -->
<dimen name="list_menu_item_icon_max_width">24dp</dimen>
+
+ <!-- Default height of desktop view header for freeform tasks on launch. -->
+ <dimen name="desktop_view_default_header_height">40dp</dimen>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cb3dfc70d135..d94d659446ac 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5412,7 +5412,7 @@
<!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
<string name="package_installed_device_owner">Installed by your admin.\nGo to settings to view granted permissions</string>
<!-- Notification shown when device owner silently updates a package [CHAR LIMIT=NONE] -->
- <string name="package_updated_device_owner">Updated by your admin</string>
+ <string name="package_updated_device_owner">Updated by your admin.\nGo to settings to view granted permissions</string>
<!-- Notification shown when device owner silently deletes a package [CHAR LIMIT=NONE] -->
<string name="package_deleted_device_owner">Deleted by your admin</string>
@@ -6226,6 +6226,18 @@
<string name="accessibility_autoclick_pause">Pause</string>
<!-- Label for autoclick position button [CHAR LIMIT=NONE] -->
<string name="accessibility_autoclick_position">Position</string>
+ <!-- Label for autoclick scroll up button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_scroll_up">Scroll Up</string>
+ <!-- Label for autoclick scroll down button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_scroll_down">Scroll Down</string>
+ <!-- Label for autoclick scroll left button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_scroll_left">Scroll Left</string>
+ <!-- Label for autoclick scroll right button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_scroll_right">Scroll Right</string>
+ <!-- Label for autoclick scroll exit button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_scroll_exit">Exit Scroll Mode</string>
+ <!-- Label for autoclick scroll panel title [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_scroll_panel_title">Scroll Panel</string>
<!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] -->
<string name="as_app_forced_to_restricted_bucket">
@@ -6698,6 +6710,8 @@ ul.</string>
<string name="profile_label_test">Test</string>
<!-- Communal profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
<string name="profile_label_communal">Communal</string>
+ <!-- Supervising profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] -->
+ <string name="profile_label_supervising">Supervising</string>
<!-- Accessibility label for managed profile user type [CHAR LIMIT=30] -->
<string name="accessibility_label_managed_profile">Work profile</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index ee1edda838fd..1c3529fa7258 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1761,6 +1761,19 @@ please see styles_device_defaults.xml.
<item name="android:tint">@color/materialColorPrimary</item>
</style>
+ <style name="AccessibilityAutoclickScrollPanelButtonLayoutStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">@dimen/accessibility_autoclick_scroll_panel_button_size </item>
+ <item name="android:layout_height">@dimen/accessibility_autoclick_scroll_panel_button_size</item>
+ </style>
+
+ <style name="AccessibilityAutoclickScrollPanelImageButtonStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:scaleType">center</item>
+ </style>
+
<!--
TODO(b/309578419): Make activities go edge-to-edge properly and then remove this.
-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a18c1d4df98b..bab4a3d178cb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1129,6 +1129,7 @@
<java-symbol type="string" name="profile_label_work_3" />
<java-symbol type="string" name="profile_label_test" />
<java-symbol type="string" name="profile_label_communal" />
+ <java-symbol type="string" name="profile_label_supervising" />
<java-symbol type="string" name="accessibility_label_managed_profile" />
<java-symbol type="string" name="accessibility_label_private_profile" />
<java-symbol type="string" name="accessibility_label_clone_profile" />
@@ -2071,6 +2072,7 @@
<java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
<java-symbol type="bool" name="config_keepDreamingWhenUnplugging" />
<java-symbol type="bool" name="config_glanceableHubEnabled" />
+ <java-symbol type="integer" name="config_whenToStartHubModeDefault" />
<java-symbol type="integer" name="config_keyguardDrawnTimeout" />
<java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
@@ -3016,6 +3018,7 @@
<java-symbol type="integer" name="config_defaultNightMode" />
+ <java-symbol type="integer" name="config_jobSchedulerBackgroundJobsDelay" />
<java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
<java-symbol type="integer" name="config_jobSchedulerInactivityIdleThresholdOnStablePower" />
<java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
@@ -3140,6 +3143,7 @@
<java-symbol type="color" name="chooser_row_divider" />
<java-symbol type="layout" name="chooser_row_direct_share" />
<java-symbol type="bool" name="config_supportDoubleTapWake" />
+ <java-symbol type="bool" name="config_supportDoubleTapSleep" />
<java-symbol type="drawable" name="ic_perm_device_info" />
<java-symbol type="string" name="config_radio_access_family" />
<java-symbol type="string" name="notification_inbox_ellipsis" />
@@ -3266,6 +3270,10 @@
<java-symbol type="dimen" name="notification_2025_content_margin_start" />
<java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" />
<java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" />
+ <java-symbol type="dimen" name="notification_2025_expand_button_right_icon_spacing" />
+ <java-symbol type="dimen" name="notification_2025_right_icon_expanded_margin_end" />
+ <java-symbol type="dimen" name="notification_2025_action_list_margin_bottom" />
+ <java-symbol type="dimen" name="notification_2025_smart_reply_container_margin" />
<java-symbol type="dimen" name="notification_progress_margin_horizontal" />
<java-symbol type="dimen" name="notification_header_background_height" />
<java-symbol type="dimen" name="notification_header_touchable_height" />
@@ -3492,6 +3500,7 @@
<java-symbol type="dimen" name="input_extract_action_button_height" />
<java-symbol type="dimen" name="notification_action_list_height" />
+ <java-symbol type="dimen" name="notification_2025_action_list_height" />
<java-symbol type="dimen" name="notification_action_emphasized_height" />
<!-- TV Remote Service package -->
@@ -3956,6 +3965,7 @@
<java-symbol type="dimen" name="notification_big_picture_max_width"/>
<java-symbol type="dimen" name="notification_right_icon_size"/>
<java-symbol type="dimen" name="notification_right_icon_content_margin"/>
+ <java-symbol type="dimen" name="notification_2025_right_icon_content_margin"/>
<java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
@@ -4159,6 +4169,17 @@
<java-symbol type="string" name="config_headlineFontFamily" />
<java-symbol type="string" name="config_headlineFontFamilyMedium" />
+ <java-symbol type="integer" name="config_pressedStateDurationMillis" />
+ <java-symbol type="integer" name="config_tapTimeoutMillis" />
+ <java-symbol type="integer" name="config_jumpTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_doubleTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_doubleTapMinTimeMillis" />
+ <java-symbol type="integer" name="config_hoverTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_zoomControlsTimeoutMillis" />
+ <java-symbol type="integer" name="config_defaultActionModeHideDurationMillis" />
+ <java-symbol type="dimen" name="config_hoverTapSlop" />
+ <java-symbol type="dimen" name="config_scrollFriction" />
+
<java-symbol type="drawable" name="stat_sys_vitals" />
<java-symbol type="color" name="text_color_primary" />
@@ -4430,6 +4451,11 @@
<!-- For Attention Service -->
<java-symbol type="integer" name="config_attentionMaximumExtension" />
+ <java-symbol type="bool" name="config_defaultPreventScreenTimeoutEnabled" />
+ <java-symbol type="integer" name="config_defaultPreventScreenTimeoutForMillis" />
+ <java-symbol type="integer" name="config_defaultMaxDurationBetweenUndimsMillis" />
+ <java-symbol type="integer" name="config_defaultUndimsRequired" />
+
<java-symbol type="string" name="config_incidentReportApproverPackage" />
<java-symbol type="array" name="config_restrictedImagesServices" />
@@ -5654,6 +5680,32 @@
<java-symbol type="drawable" name="accessibility_autoclick_resume" />
<java-symbol type="drawable" name="ic_accessibility_autoclick" />
+ <!-- Accessibility autoclick scroll panel related -->
+ <java-symbol type="layout" name="accessibility_autoclick_scroll_panel" />
+ <java-symbol type="dimen" name="accessibility_autoclick_scroll_panel_button_size" />
+ <java-symbol type="drawable" name="accessibility_autoclick_scroll_up" />
+ <java-symbol type="drawable" name="accessibility_autoclick_scroll_down" />
+ <java-symbol type="drawable" name="accessibility_autoclick_scroll_left" />
+ <java-symbol type="drawable" name="accessibility_autoclick_scroll_right" />
+ <java-symbol type="drawable" name="accessibility_autoclick_scroll_exit" />
+ <java-symbol type="string" name="accessibility_autoclick_scroll_up" />
+ <java-symbol type="string" name="accessibility_autoclick_scroll_down" />
+ <java-symbol type="string" name="accessibility_autoclick_scroll_left" />
+ <java-symbol type="string" name="accessibility_autoclick_scroll_right" />
+ <java-symbol type="string" name="accessibility_autoclick_scroll_exit" />
+ <java-symbol type="string" name="accessibility_autoclick_scroll_panel_title" />
+ <java-symbol type="id" name="accessibility_autoclick_scroll_panel" />
+ <java-symbol type="id" name="scroll_up_layout" />
+ <java-symbol type="id" name="scroll_down_layout" />
+ <java-symbol type="id" name="scroll_left_layout" />
+ <java-symbol type="id" name="scroll_right_layout" />
+ <java-symbol type="id" name="scroll_exit_layout" />
+ <java-symbol type="id" name="scroll_up" />
+ <java-symbol type="id" name="scroll_down" />
+ <java-symbol type="id" name="scroll_left" />
+ <java-symbol type="id" name="scroll_right" />
+ <java-symbol type="id" name="scroll_exit" />
+
<!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
<java-symbol type="xml" name="haptic_feedback_customization" />
@@ -5927,4 +5979,6 @@
<!-- Enable OEMs to support scale up anim across tasks.-->
<java-symbol type="bool" name="config_enableCrossTaskScaleUpAnimation" />
+ <!-- Default height of desktop view header for freeform tasks on launch. -->
+ <java-symbol type="dimen" name="desktop_view_default_header_height" />
</resources>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index fa0744775f6d..5af837fce612 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -18,7 +18,7 @@ package com.android.server.broadcastradio.hal2;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static org.junit.Assert.*;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
diff --git a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
index e8b295bd5fdb..0287e6c086aa 100644
--- a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java
@@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.hardware.usb.UsbDevice;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -118,6 +118,6 @@ public class BrailleDisplayControllerImplTest {
verify(mBrailleDisplayCallback).onConnectionFailed(
BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS);
- verifyZeroInteractions(mAccessibilityServiceConnection);
+ verifyNoMoreInteractions(mAccessibilityServiceConnection);
}
}
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index 33a46d0fde17..6d86bd209a3d 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.os.Handler;
+import android.os.Looper;
import android.util.PollingCheck;
import android.view.View;
@@ -486,6 +488,42 @@ public class AnimatorSetCallsTest {
});
}
+ @Test
+ public void testCancelOnPendingEndListener() throws Throwable {
+ final CountDownLatch endLatch = new CountDownLatch(1);
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final boolean[] endCalledRightAfterCancel = new boolean[2];
+ final AnimatorSet set = new AnimatorSet();
+ final ValueAnimatorTests.MyListener asListener = new ValueAnimatorTests.MyListener();
+ final ValueAnimatorTests.MyListener vaListener = new ValueAnimatorTests.MyListener();
+ final ValueAnimator va = new ValueAnimator();
+ va.setFloatValues(0f, 1f);
+ va.setDuration(30);
+ va.addUpdateListener(animation -> {
+ if (animation.getAnimatedFraction() == 1f) {
+ handler.post(() -> {
+ set.cancel();
+ endCalledRightAfterCancel[0] = vaListener.endCalled;
+ endCalledRightAfterCancel[1] = asListener.endCalled;
+ endLatch.countDown();
+ });
+ }
+ });
+ set.addListener(asListener);
+ va.addListener(vaListener);
+ set.play(va);
+
+ ValueAnimator.setPostNotifyEndListenerEnabled(true);
+ try {
+ handler.post(set::start);
+ assertTrue(endLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(endCalledRightAfterCancel[0]);
+ assertTrue(endCalledRightAfterCancel[1]);
+ } finally {
+ ValueAnimator.setPostNotifyEndListenerEnabled(false);
+ }
+ }
+
private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
final boolean[] value = new boolean[1];
PollingCheck.waitFor(() -> {
diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index 04698465e971..a55909f0c193 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -922,6 +922,36 @@ public class ValueAnimatorTests {
}
@Test
+ public void testCancelOnPendingEndListener() throws Throwable {
+ final CountDownLatch endLatch = new CountDownLatch(1);
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final boolean[] endCalledRightAfterCancel = new boolean[1];
+ final MyListener listener = new MyListener();
+ final ValueAnimator va = new ValueAnimator();
+ va.setFloatValues(0f, 1f);
+ va.setDuration(30);
+ va.addUpdateListener(animation -> {
+ if (animation.getAnimatedFraction() == 1f) {
+ handler.post(() -> {
+ va.cancel();
+ endCalledRightAfterCancel[0] = listener.endCalled;
+ endLatch.countDown();
+ });
+ }
+ });
+ va.addListener(listener);
+
+ ValueAnimator.setPostNotifyEndListenerEnabled(true);
+ try {
+ handler.post(va::start);
+ assertThat(endLatch.await(1, TimeUnit.SECONDS)).isTrue();
+ assertThat(endCalledRightAfterCancel[0]).isTrue();
+ } finally {
+ ValueAnimator.setPostNotifyEndListenerEnabled(false);
+ }
+ }
+
+ @Test
public void testZeroDuration() throws Throwable {
// Run two animators with zero duration, with one running forward and the other one
// backward. Check that the animations start and finish with the correct end fractions.
@@ -1182,6 +1212,7 @@ public class ValueAnimatorTests {
assertEquals(A1_START_VALUE, a1.getAnimatedValue());
});
}
+
class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener {
boolean wasRunning = false;
long firstRunningFrameTime = -1;
@@ -1207,7 +1238,7 @@ public class ValueAnimatorTests {
}
}
- class MyListener implements Animator.AnimatorListener {
+ static class MyListener implements Animator.AnimatorListener {
boolean startCalled = false;
boolean cancelCalled = false;
boolean endCalled = false;
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index f89e4416ce78..157c74abc5de 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -371,22 +371,22 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
- public void testHasPromotableStyle_bigPicture() {
+ public void testHasPromotableStyle_bigText() {
Notification n = new Notification.Builder(mContext, "test")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setStyle(new Notification.BigPictureStyle())
+ .setStyle(new Notification.BigTextStyle())
.build();
assertThat(n.hasPromotableStyle()).isTrue();
}
@Test
@EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
- public void testHasPromotableStyle_bigText() {
+ public void testHasPromotableStyle_no_bigPictureStyle() {
Notification n = new Notification.Builder(mContext, "test")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setStyle(new Notification.BigTextStyle())
+ .setStyle(new Notification.BigPictureStyle())
.build();
- assertThat(n.hasPromotableStyle()).isTrue();
+ assertThat(n.hasPromotableStyle()).isFalse();
}
@Test
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index ee4761b9d024..70b4150115fe 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -233,7 +233,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
/**
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
*/
- private class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
+ public class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest";
static final String SERVICE_META_DATA = "RegisteredServicesCacheTest";
static final String ATTRIBUTES_NAME = "test";
@@ -332,7 +332,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
}
}
- static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
+ public static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
public void writeAsXml(TestServiceType item, TypedXmlSerializer out) throws IOException {
out.attribute(null, "type", item.type);
@@ -347,7 +347,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
}
}
- static class TestServiceType implements Parcelable {
+ public static class TestServiceType implements Parcelable {
final String type;
final String value;
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
new file mode 100644
index 000000000000..8349659517c5
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.content.pm;
+
+import static android.content.pm.Flags.FLAG_OPTIMIZE_PARSING_IN_REGISTERED_SERVICES_CACHE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.RegisteredServicesCacheTest.TestServiceType;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.content.pm.RegisteredServicesCache}
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_OPTIMIZE_PARSING_IN_REGISTERED_SERVICES_CACHE)
+public class RegisteredServicesCacheUnitTest {
+ private static final String TAG = "RegisteredServicesCacheUnitTest";
+ private static final int U0 = 0;
+ private static final int U1 = 1;
+ private static final int UID1 = 1;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private final ResolveInfo mResolveInfo1 = new ResolveInfo();
+ private final ResolveInfo mResolveInfo2 = new ResolveInfo();
+ private final TestServiceType mTestServiceType1 = new TestServiceType("t1", "value1");
+ private final TestServiceType mTestServiceType2 = new TestServiceType("t2", "value2");
+ @Mock
+ RegisteredServicesCache.Injector<TestServiceType> mMockInjector;
+ @Mock
+ Context mMockContext;
+ Handler mMockBackgroundHandler;
+ @Mock
+ PackageManager mMockPackageManager;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockInjector.getContext()).thenReturn(mMockContext);
+ mMockBackgroundHandler = spy(BackgroundThread.getHandler());
+ when(mMockInjector.getBackgroundHandler()).thenReturn(mMockBackgroundHandler);
+ doReturn(mock(Intent.class)).when(mMockContext).registerReceiverAsUser(any(), any(), any(),
+ any(), any());
+ doReturn(mock(Intent.class)).when(mMockContext).registerReceiver(any(), any(), any(),
+ any());
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+ addServiceInfoIntoResolveInfo(mResolveInfo1, "r1.package.name" /* packageName */,
+ "r1.service.name" /* serviceName */);
+ addServiceInfoIntoResolveInfo(mResolveInfo2, "r2.package.name" /* packageName */,
+ "r2.service.name" /* serviceName */);
+ }
+
+ @Test
+ public void testSaveServiceInfoIntoCaches() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+ PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo2.serviceInfo.packageName),
+ anyInt(), eq(U1))).thenReturn(packageInfo2);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ int u1uid = UserHandle.getUid(U1, UID1);
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo(
+ mTestServiceType2, u1uid, mResolveInfo2.serviceInfo.getComponentName(),
+ 2000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U1, mResolveInfo2, serviceInfo2);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.invalidateCache(U1);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+ }
+
+ @Test
+ public void testClearServiceInfoCachesAfterRemoveUserId() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.onUserRemoved(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ }
+
+ @Test
+ public void testGetServiceInfoCachesForMultiUser() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U1))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.clearServicesForQuerying();
+ int u1uid = UserHandle.getUid(U1, UID1);
+ assertThat(u1uid).isNotEqualTo(UID1);
+
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo(
+ mTestServiceType1, u1uid, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U1, mResolveInfo1, serviceInfo2);
+
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.invalidateCache(U1);
+
+ // There is a bug to return the same info from the cache for different users. Make sure it
+ // will return the different info from the cache for different users.
+ Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> serviceInfos;
+ serviceInfos = testServicesCache.getAllServices(U0);
+ // Make sure the service info is retrieved from the cache for U0.
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ for (RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo : serviceInfos) {
+ assertThat(serviceInfo.componentInfo.applicationInfo.uid).isEqualTo(UID1);
+ }
+
+ serviceInfos = testServicesCache.getAllServices(U1);
+ // Make sure the service info is retrieved from the cache for U1.
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+ for (RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo : serviceInfos) {
+ assertThat(serviceInfo.componentInfo.applicationInfo.uid).isEqualTo(u1uid);
+ }
+ }
+
+ @Test
+ public void testUpdateServiceInfoIntoCachesWhenPackageInfoNotFound() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+ reset(mMockPackageManager);
+
+ doThrow(new SecurityException("")).when(mMockPackageManager).getPackageInfoAsUser(
+ eq(mResolveInfo1.serviceInfo.packageName), anyInt(), eq(U0));
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), anyLong());
+ }
+
+ @Test
+ public void testUpdateServiceInfoIntoCachesWhenTheApplicationHasBeenUpdated() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ reset(testServicesCache);
+ reset(mMockPackageManager);
+
+ PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo2);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(2000L));
+ }
+
+ @Test
+ public void testClearServiceInfoCachesAfterTimeout() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ // Immediately invoke run on the Runnable posted to the handler
+ doAnswer(invocation -> {
+ Message message = invocation.getArgument(0);
+ message.getCallback().run();
+ return true;
+ }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong());
+
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ verify(mMockBackgroundHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ }
+
+ private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
+ TestServiceType type, int uid, ComponentName componentName, long lastUpdateTime) {
+ final ComponentInfo info = new ComponentInfo();
+ info.applicationInfo = new ApplicationInfo();
+ info.applicationInfo.uid = uid;
+ return new RegisteredServicesCache.ServiceInfo<>(type, info, componentName, lastUpdateTime);
+ }
+
+ private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ String serviceName) {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = serviceName;
+ resolveInfo.serviceInfo = serviceInfo;
+ }
+
+ private PackageInfo createPackageInfo(long lastUpdateTime) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.lastUpdateTime = lastUpdateTime;
+ return packageInfo;
+ }
+
+ /**
+ * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
+ */
+ public class TestRegisteredServicesCache extends RegisteredServicesCache<TestServiceType> {
+ static final String SERVICE_INTERFACE = "RegisteredServicesCacheUnitTest";
+ static final String SERVICE_META_DATA = "RegisteredServicesCacheUnitTest";
+ static final String ATTRIBUTES_NAME = "test";
+ private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices =
+ new SparseArray<>();
+
+ public TestRegisteredServicesCache(Injector<TestServiceType> injector,
+ XmlSerializerAndParser<TestServiceType> serializerAndParser) {
+ super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME,
+ serializerAndParser);
+ }
+
+ @Override
+ public TestServiceType parseServiceAttributes(Resources res, String packageName,
+ AttributeSet attrs) {
+ return null;
+ }
+
+ @Override
+ protected List<ResolveInfo> queryIntentServices(int userId) {
+ Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId,
+ new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>());
+ return new ArrayList<>(map.keySet());
+ }
+
+ void addServiceForQuerying(int userId, ResolveInfo resolveInfo,
+ ServiceInfo<TestServiceType> serviceInfo) {
+ Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId);
+ if (map == null) {
+ map = new HashMap<>();
+ mServices.put(userId, map);
+ }
+ map.put(resolveInfo, serviceInfo);
+ }
+
+ void clearServicesForQuerying() {
+ mServices.clear();
+ }
+
+ @Override
+ protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+ long lastUpdateTime) throws XmlPullParserException, IOException {
+ int size = mServices.size();
+ for (int i = 0; i < size; i++) {
+ Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
+ ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo);
+ if (serviceInfo != null) {
+ return serviceInfo;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected service " + resolveInfo);
+ }
+
+ @Override
+ public void onUserRemoved(int userId) {
+ super.onUserRemoved(userId);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index de5f0ffbe23f..34650be331d3 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -264,7 +264,7 @@ public class DisplayManagerGlobalTest {
/* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
- Mockito.verifyZeroInteractions(mDisplayListener);
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
@@ -272,7 +272,7 @@ public class DisplayManagerGlobalTest {
/* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
waitForHandler();
- Mockito.verifyZeroInteractions(mDisplayListener);
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
@@ -280,7 +280,7 @@ public class DisplayManagerGlobalTest {
/* isEventFilterExplicit */ true);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
- Mockito.verifyZeroInteractions(mDisplayListener);
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
}
@Test
diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
index 46f22cec4213..504786111efe 100644
--- a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
+++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
@@ -20,8 +20,8 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.fail;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import android.Manifest.permission;
import android.content.Context;
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 3e6520106ab0..bb059108d4b6 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -29,6 +29,8 @@ import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -416,4 +418,63 @@ public class ParcelTest {
int binderEndPos = pA.dataPosition();
assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos));
}
+
+ private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42};
+
+ // Allow for some Parcel overhead
+ private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100;
+
+ @Test
+ public void testMarshall_ByteBuffer_wrapped() {
+ ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH);
+ testMarshall_ByteBuffer(bb);
+ }
+
+ @Test
+ public void testMarshall_DirectByteBuffer() {
+ ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH);
+ testMarshall_ByteBuffer(bb);
+ }
+
+ private void testMarshall_ByteBuffer(ByteBuffer bb) {
+ // Ensure that Parcel respects the starting offset by not starting at 0
+ bb.position(1);
+ bb.mark();
+
+ // Parcel test data, then marshall into the ByteBuffer
+ Parcel p1 = Parcel.obtain();
+ p1.writeByteArray(TEST_DATA);
+ p1.marshall(bb);
+ p1.recycle();
+
+ assertTrue(bb.position() > 1);
+ bb.reset();
+
+ // Unmarshall test data into a new Parcel
+ Parcel p2 = Parcel.obtain();
+ bb.reset();
+ p2.unmarshall(bb);
+ assertTrue(bb.position() > 1);
+ p2.setDataPosition(0);
+ byte[] marshalled = p2.marshall();
+
+ bb.reset();
+ for (int i = 0; i < TEST_DATA.length; i++) {
+ assertEquals(bb.get(), marshalled[i]);
+ }
+
+ byte[] testDataCopy = new byte[TEST_DATA.length];
+ p2.setDataPosition(0);
+ p2.readByteArray(testDataCopy);
+ for (int i = 0; i < TEST_DATA.length; i++) {
+ assertEquals(TEST_DATA[i], testDataCopy[i]);
+ }
+
+ // Test that overflowing the buffer throws an exception
+ bb.reset();
+ // Leave certainly not enough room for the test data
+ bb.limit(bb.position() + TEST_DATA.length - 1);
+ assertThrows(BufferOverflowException.class, () -> p2.marshall(bb));
+ p2.recycle();
+ }
}
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
index 21a22057d7c8..f5209952508c 100644
--- a/core/tests/coretests/src/android/provider/FontsContractTest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractTest.java
@@ -21,8 +21,8 @@ import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABL
import static android.provider.FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY;
import static android.provider.FontsContract.Columns.RESULT_CODE_OK;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 9e78af57b470..11ec9f8e1912 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -16,6 +16,9 @@
package android.text;
+import static android.text.Layout.HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR;
+import static android.text.Layout.HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP;
+
import static com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT;
import static org.junit.Assert.assertArrayEquals;
@@ -1073,6 +1076,68 @@ public class LayoutTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testRoundedRectSize_belowMinimum_usesMinimumValue() {
+ mTextPaint.setColor(Color.BLACK);
+ mTextPaint.setTextSize(8); // Value chosen so that N * RADIUS_FACTOR < RADIUS_MIN_DP
+ Layout layout = new StaticLayout("Test text", mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+
+ final float expectedRoundedRectSize =
+ mTextPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP;
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+ if (drawCommand.rect != null) {
+ expect.that(drawCommand.rX).isEqualTo(expectedRoundedRectSize);
+ expect.that(drawCommand.rY).isEqualTo(expectedRoundedRectSize);
+ }
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testRoundedRectSize_aboveMinimum_usesScaledValue() {
+ mTextPaint.setColor(Color.BLACK);
+ mTextPaint.setTextSize(50); // Value chosen so that N * RADIUS_FACTOR > RADIUS_MIN_DP
+ Layout layout = new StaticLayout("Test text", mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+
+ final float expectedRoundedRectSize =
+ mTextPaint.getTextSize() * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR;
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+ if (drawCommand.rect != null) {
+ expect.that(drawCommand.rX).isEqualTo(expectedRoundedRectSize);
+ expect.that(drawCommand.rY).isEqualTo(expectedRoundedRectSize);
+ }
+ }
+ }
+
private int removeAlpha(int color) {
return Color.rgb(
Color.red(color),
@@ -1087,6 +1152,8 @@ public class LayoutTest {
public final String text;
public final float x;
public final float y;
+ public final float rX;
+ public final float rY;
public final Path path;
public final RectF rect;
public final Paint paint;
@@ -1098,6 +1165,8 @@ public class LayoutTest {
this.paint = new Paint(paint);
path = null;
rect = null;
+ this.rX = 0;
+ this.rY = 0;
}
DrawCommand(Path path, Paint paint) {
@@ -1107,15 +1176,19 @@ public class LayoutTest {
x = 0;
text = null;
rect = null;
+ this.rX = 0;
+ this.rY = 0;
}
- DrawCommand(RectF rect, Paint paint) {
+ DrawCommand(RectF rect, Paint paint, float rX, float rY) {
this.rect = new RectF(rect);
this.paint = new Paint(paint);
path = null;
y = 0;
x = 0;
text = null;
+ this.rX = rX;
+ this.rY = rY;
}
@Override
@@ -1189,12 +1262,12 @@ public class LayoutTest {
@Override
public void drawRect(RectF rect, Paint p) {
- mDrawCommands.add(new DrawCommand(rect, p));
+ mDrawCommands.add(new DrawCommand(rect, p, 0, 0));
}
@Override
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
- mDrawCommands.add(new DrawCommand(rect, paint));
+ mDrawCommands.add(new DrawCommand(rect, paint, rx, ry));
}
List<DrawCommand> getDrawCommands() {
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 711ff9458e11..c7efe6f93f0d 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -14,14 +14,18 @@
package android.util;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +36,7 @@ import java.util.ConcurrentModificationException;
* Unit tests for ArrayMap that don't belong in CTS.
*/
@LargeTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class ArrayMapTest {
private static final String TAG = "ArrayMapTest";
@@ -51,6 +56,7 @@ public class ArrayMapTest {
* @throws Exception
*/
@Test
+ @Ignore("Failing; b/399137661")
@IgnoreUnderRavenwood(reason = "Long test runtime")
public void testConcurrentModificationException() throws Exception {
final int TEST_LEN_MS = 5000;
@@ -118,4 +124,49 @@ public class ArrayMapTest {
}
}
}
+
+ @Test
+ public void testToString() {
+ map.put("1", "One");
+ map.put("2", "Two");
+ map.put("3", "Five");
+ map.put("3", "Three");
+
+ assertThat(map.toString()).isEqualTo("{1=One, 2=Two, 3=Three}");
+ assertThat(map.keySet().toString()).isEqualTo("[1, 2, 3]");
+ assertThat(map.values().toString()).isEqualTo("[One, Two, Three]");
+ }
+
+ @Test
+ public void testToStringRecursive() {
+ ArrayMap<Object, Object> weird = new ArrayMap<>();
+ weird.put("1", weird);
+
+ assertThat(weird.toString()).isEqualTo("{1=(this Map)}");
+ assertThat(weird.keySet().toString()).isEqualTo("[1]");
+ assertThat(weird.values().toString()).isEqualTo("[{1=(this Map)}]");
+ }
+
+ @Test
+ public void testToStringRecursiveKeySet() {
+ ArrayMap<Object, Object> weird = new ArrayMap<>();
+ weird.put("1", weird.keySet());
+ weird.put(weird.keySet(), "2");
+
+ assertThat(weird.toString()).isEqualTo("{1=[1, (this KeySet)], [1, (this KeySet)]=2}");
+ assertThat(weird.keySet().toString()).isEqualTo("[1, (this KeySet)]");
+ assertThat(weird.values().toString()).isEqualTo("[[1, (this KeySet)], 2]");
+ }
+
+ @Test
+ public void testToStringRecursiveValues() {
+ ArrayMap<Object, Object> weird = new ArrayMap<>();
+ weird.put("1", weird.values());
+ weird.put(weird.values(), "2");
+
+ assertThat(weird.toString()).isEqualTo(
+ "{1=[(this ValuesCollection), 2], [(this ValuesCollection), 2]=2}");
+ assertThat(weird.keySet().toString()).isEqualTo("[1, [(this ValuesCollection), 2]]");
+ assertThat(weird.values().toString()).isEqualTo("[(this ValuesCollection), 2]");
+ }
}
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 8ac9292390b0..50cd4c00c2ce 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.CancellationSignal;
@@ -230,7 +230,7 @@ public class PendingInsetsControllerTest {
InsetsController secondController = mock(InsetsController.class);
mPendingInsetsController.replayAndAttach(secondController);
verify(mReplayedController).show(eq(systemBars()));
- verifyZeroInteractions(secondController);
+ verifyNoMoreInteractions(secondController);
}
@Test
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index e5ad5613af2d..341947c900c5 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -29,8 +29,8 @@ import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyObject;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -79,7 +79,7 @@ public class AccessibilityCacheTest {
@Before
public void setUp() {
mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
- when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
+ when(mAccessibilityNodeRefresher.refreshNode(any(), anyBoolean())).thenReturn(true);
mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
}
@@ -854,7 +854,7 @@ public class AccessibilityCacheTest {
try {
assertEventTypeClearsNode(eventType, false);
verify(mAccessibilityNodeRefresher, never())
- .refreshNode(anyObject(), anyBoolean());
+ .refreshNode(any(), anyBoolean());
} catch (Throwable e) {
throw new AssertionError(
"Failed for eventType: " + AccessibilityEvent.eventTypeToString(
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index eb482f2e0aa5..f811d8efedeb 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
import android.os.Bundle;
@@ -74,7 +74,7 @@ public class AccessibilityInteractionClientTest {
MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
assertEquals("Node got lost along the way", nodeFromConnection, node);
- verifyZeroInteractions(mMockCache);
+ verifyNoMoreInteractions(mMockCache);
}
@Test
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index 82e34275c66c..551357c6ba7a 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -34,8 +34,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index 5f89f9c14793..8bbe81da512b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -130,7 +130,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
assertThat(session.mContentProtectionEventProcessor).isNull();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
}
@Test
@@ -151,7 +151,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
assertThat(session.mContentProtectionEventProcessor).isNull();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
}
@Test
@@ -172,7 +172,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
assertThat(session.mContentProtectionEventProcessor).isNull();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
}
@Test
@@ -197,7 +197,7 @@ public class MainContentCaptureSessionTest {
session.sendEvent(EVENT);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -227,7 +227,7 @@ public class MainContentCaptureSessionTest {
session.sendEvent(EVENT);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNotNull();
assertThat(session.mEvents).containsExactly(EVENT);
}
@@ -255,7 +255,7 @@ public class MainContentCaptureSessionTest {
session.sendEvent(EVENT);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -272,8 +272,8 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
- verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
assertThat(session.mEvents).containsExactly(EVENT);
}
@@ -289,8 +289,8 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
- verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
assertThat(session.mEvents).containsExactly(EVENT);
}
@@ -307,7 +307,7 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isEmpty();
assertEventFlushedContentCapture(options);
}
@@ -325,7 +325,7 @@ public class MainContentCaptureSessionTest {
session.flush(REASON);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isEmpty();
assertEventFlushedContentCapture(options);
}
@@ -339,7 +339,7 @@ public class MainContentCaptureSessionTest {
mTestableLooper.processAllMessages();
verify(mMockSystemServerInterface).finishSession(anyInt());
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mDirectServiceInterface).isNull();
assertThat(session.mContentProtectionEventProcessor).isNull();
}
@@ -352,8 +352,8 @@ public class MainContentCaptureSessionTest {
session.resetSession(/* newState= */ 0);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockSystemServerInterface);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockSystemServerInterface);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mDirectServiceInterface).isNull();
assertThat(session.mContentProtectionEventProcessor).isNull();
}
@@ -370,8 +370,8 @@ public class MainContentCaptureSessionTest {
notifyContentCaptureEvents(session);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentCaptureDirectManager);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -388,8 +388,8 @@ public class MainContentCaptureSessionTest {
notifyContentCaptureEvents(session);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentCaptureDirectManager);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
@@ -407,8 +407,8 @@ public class MainContentCaptureSessionTest {
notifyContentCaptureEvents(session);
mTestableLooper.processAllMessages();
- verifyZeroInteractions(mMockContentCaptureDirectManager);
- verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyNoMoreInteractions(mMockContentCaptureDirectManager);
+ verifyNoMoreInteractions(mMockContentProtectionEventProcessor);
assertThat(session.mEvents).isNull();
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index ba0dbf454ad2..e75452cafdab 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -443,7 +442,7 @@ public class ContentProtectionEventProcessorTest {
mTestLooper.dispatchAll();
verify(mMockEventBuffer, never()).clear();
verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
+ verifyNoMoreInteractions(mMockContentCaptureManager);
}
private void assertLoginDetected() throws Exception {
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 3eb7d9aa738a..34ccc8bb5179 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -51,7 +51,7 @@ import static junit.framework.Assert.assertTrue;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
index b61d86819c17..3570c2e0ace0 100644
--- a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
@@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.app.Activity;
import android.app.Instrumentation;
@@ -159,7 +158,7 @@ public class TextViewReceiveContentTest {
ContentInfo payload =
new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
}
@Test
@@ -180,19 +179,19 @@ public class TextViewReceiveContentTest {
ContentInfo payload =
new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
payload = new ContentInfo.Builder(clip, SOURCE_INPUT_METHOD).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
payload = new ContentInfo.Builder(clip, SOURCE_DRAG_AND_DROP).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
payload = new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build();
mDefaultReceiver.onReceiveContent(mEditText, payload);
- verifyZeroInteractions(ic.mMock);
+ verifyNoMoreInteractions(ic.mMock);
}
private static class MyInputConnection extends InputConnectionWrapper {
diff --git a/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt b/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt
index 381b566018c7..ad68e385459e 100644
--- a/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt
+++ b/core/tests/coretests/src/android/window/BackTouchTrackerTest.kt
@@ -37,7 +37,7 @@ class BackTouchTrackerTest {
fun generatesProgress_onStart() {
val linearTracker = linearTouchTracker()
linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
- val event = linearTracker.createStartEvent(null)
+ val event = linearTracker.createStartEvent()
assertEquals(0f, event.progress, 0f)
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 4d6c30ebbe2b..66524d1c1d2a 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -541,24 +541,30 @@ public class WindowOnBackInvokedDispatcherTest {
throws RemoteException, InterruptedException {
// Setup a callback that unregisters itself after the gesture is finished but before the
// progress is animated back to 0f
- final AtomicBoolean unregisterOnProgressUpdate = new AtomicBoolean(false);
+ final AtomicBoolean unregisterOnNextCallbackInvocation = new AtomicBoolean(false);
final AtomicInteger onBackInvokedCalled = new AtomicInteger(0);
final CountDownLatch onBackCancelledCalled = new CountDownLatch(1);
OnBackAnimationCallback onBackAnimationCallback = new OnBackAnimationCallback() {
@Override
public void onBackProgressed(@NonNull BackEvent backEvent) {
- if (unregisterOnProgressUpdate.get()) {
+ if (unregisterOnNextCallbackInvocation.getAndSet(false)) {
mDispatcher.unregisterOnBackInvokedCallback(this);
}
}
@Override
public void onBackInvoked() {
+ if (unregisterOnNextCallbackInvocation.getAndSet(false)) {
+ mDispatcher.unregisterOnBackInvokedCallback(this);
+ }
onBackInvokedCalled.getAndIncrement();
}
@Override
public void onBackCancelled() {
+ if (unregisterOnNextCallbackInvocation.getAndSet(false)) {
+ mDispatcher.unregisterOnBackInvokedCallback(this);
+ }
onBackCancelledCalled.countDown();
}
};
@@ -572,7 +578,7 @@ public class WindowOnBackInvokedDispatcherTest {
// simulate back gesture finished and onBackCancelled() called, which starts the progress
// animation back to 0f. On the first progress emission, the callback will unregister itself
- unregisterOnProgressUpdate.set(true);
+ unregisterOnNextCallbackInvocation.set(true);
callbackInfo.getCallback().onBackCancelled();
waitForIdle();
onBackCancelledCalled.await(1000, TimeUnit.MILLISECONDS);
@@ -689,8 +695,7 @@ public class WindowOnBackInvokedDispatcherTest {
/* frameTimeMillis = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
- /* swipeEdge = */ BackEvent.EDGE_LEFT,
- /* departingAnimationTarget = */ null);
+ /* swipeEdge = */ BackEvent.EDGE_LEFT);
}
private void verifyImeCallackRegistrations() throws RemoteException {
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 74b4de1833ea..1977ff52c7c5 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHO
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
@@ -34,17 +35,16 @@ import static org.junit.Assert.fail;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -165,7 +165,7 @@ public class AccessibilityShortcutControllerTest {
.thenReturn(accessibilityManager);
when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
.thenReturn(mAlertDialogBuilder);
- when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
+ when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), any(), anyInt()))
.thenReturn(mToast);
when(mFrameworkObjectProvider.getSystemUiContext()).thenReturn(mContext);
when(mFrameworkObjectProvider.getTextToSpeech(eq(mContext), any()))
@@ -179,20 +179,20 @@ public class AccessibilityShortcutControllerTest {
ResolveInfo resolveInfo = mock(ResolveInfo.class);
resolveInfo.serviceInfo = mock(ServiceInfo.class);
resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
- when(resolveInfo.loadLabel(anyObject())).thenReturn(PACKAGE_NAME_STRING);
+ when(resolveInfo.loadLabel(any())).thenReturn(PACKAGE_NAME_STRING);
when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
when(mServiceInfo.getComponentName())
.thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
when(mServiceInfo.loadSummary(any())).thenReturn(SERVICE_NAME_SUMMARY);
- when(mAlertDialogBuilder.setTitle(anyObject())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setTitle(any())).thenReturn(mAlertDialogBuilder);
when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
- when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
- when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
+ when(mAlertDialogBuilder.setMessage(any())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setPositiveButton(anyInt(), any()))
.thenReturn(mAlertDialogBuilder);
- when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject()))
+ when(mAlertDialogBuilder.setNegativeButton(anyInt(), any()))
.thenReturn(mAlertDialogBuilder);
- when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setOnCancelListener(any())).thenReturn(mAlertDialogBuilder);
when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog);
mLayoutParams.privateFlags = 0;
@@ -348,7 +348,7 @@ public class AccessibilityShortcutControllerTest {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
AccessibilityShortcutController accessibilityShortcutController = getController();
accessibilityShortcutController.performAccessibilityShortcut();
- verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
+ verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), any());
}
@Test
@@ -522,7 +522,7 @@ public class AccessibilityShortcutControllerTest {
AccessibilityShortcutController.DialogStatus.SHOWN);
getController().performAccessibilityShortcut();
- verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
+ verifyNoMoreInteractions(mAlertDialogBuilder, mAlertDialog);
verify(mToast).show();
verify(mAccessibilityManagerService).performAccessibilityShortcut(
Display.DEFAULT_DISPLAY, HARDWARE, null);
@@ -615,7 +615,7 @@ public class AccessibilityShortcutControllerTest {
AccessibilityShortcutController.DialogStatus.SHOWN);
getController().performAccessibilityShortcut();
- verifyZeroInteractions(mToast);
+ verifyNoMoreInteractions(mToast);
verify(mAccessibilityManagerService).performAccessibilityShortcut(
Display.DEFAULT_DISPLAY, HARDWARE, null);
}
@@ -632,7 +632,7 @@ public class AccessibilityShortcutControllerTest {
AccessibilityShortcutController.DialogStatus.SHOWN);
getController().performAccessibilityShortcut();
- verifyZeroInteractions(mToast);
+ verifyNoMoreInteractions(mToast);
verify(mAccessibilityManagerService).performAccessibilityShortcut(
Display.DEFAULT_DISPLAY, HARDWARE, null);
}
@@ -715,6 +715,25 @@ public class AccessibilityShortcutControllerTest {
verify(mRingtone, times(0)).play();
}
+ @Test
+ public void onUserSetupComplete_noEnabledServices_blankHardwareSetting() throws Exception {
+ AccessibilityShortcutController controller = getController();
+ configureValidShortcutService();
+ // Shortcut setting should be cleared on user setup
+ Settings.Secure.putStringForUser(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, 0);
+ when(mAccessibilityManagerService
+ .getEnabledAccessibilityServiceList(anyInt(), eq(0)))
+ .thenReturn(Collections.emptyList());
+ Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 1);
+
+ controller.mUserSetupCompleteObserver.onChange(true);
+
+ final String shortcut = Settings.Secure.getStringForUser(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 0);
+ assertThat(shortcut).isEqualTo("");
+ }
+
private void configureNoShortcutService() throws Exception {
when(mAccessibilityManagerService
.getAccessibilityShortcutTargets(HARDWARE))
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
index db69cf2397fc..02a296892c53 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
@@ -72,7 +72,8 @@ public class ChooserActivityWorkProfileTest {
private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
.getInstrumentation().getTargetContext().getUser();
- private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+ private static final UserHandle WORK_USER_HANDLE =
+ UserHandle.of(PERSONAL_USER_HANDLE.getIdentifier() + 1);
@Rule
public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index d21ab44d251d..15e746cb13b6 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -87,6 +87,10 @@ public class ResolverActivityTest {
private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
.getInstrumentation().getTargetContext().getUser();
+ private static final int WORK_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 1;
+ private static final int CLONE_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 2;
+ private static final int PRIVATE_USER_ID = PERSONAL_USER_HANDLE.getIdentifier() + 3;
+
@Rule
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
@@ -247,7 +251,7 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
@@ -270,7 +274,7 @@ public class ResolverActivityTest {
};
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
// We pick the first one as there is another one in the work profile side
onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
@@ -444,7 +448,7 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
@@ -456,7 +460,7 @@ public class ResolverActivityTest {
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+ assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
// The work list adapter must be populated in advance before tapping the other tab
assertThat(activity.getWorkListAdapter().getCount(), is(4));
}
@@ -466,7 +470,7 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
@@ -478,7 +482,7 @@ public class ResolverActivityTest {
waitForIdle();
onView(withText(R.string.resolver_work_tab)).perform(click());
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(WORK_USER_ID));
assertThat(activity.getWorkListAdapter().getCount(), is(4));
}
@@ -498,7 +502,7 @@ public class ResolverActivityTest {
waitForIdle();
onView(withText(R.string.resolver_work_tab)).perform(click());
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(WORK_USER_ID));
assertThat(activity.getPersonalListAdapter().getCount(), is(2));
}
@@ -508,7 +512,7 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
sOverrides.workProfileUserHandle);
@@ -530,7 +534,7 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
sOverrides.workProfileUserHandle);
@@ -633,7 +637,7 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
sOverrides.workProfileUserHandle);
@@ -669,7 +673,7 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets,
@@ -697,7 +701,7 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets,
@@ -844,7 +848,7 @@ public class ResolverActivityTest {
public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() {
ResolverActivity.ENABLE_TABBED_VIEW = false;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -898,7 +902,7 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(2, WORK_USER_ID,
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets,
@@ -1376,15 +1380,16 @@ public class ResolverActivityTest {
}
private void markWorkProfileUserAvailable() {
- ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
+ ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(WORK_USER_ID);
}
private void markCloneProfileUserAvailable() {
- ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
+ ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(CLONE_USER_ID);
}
private void markPrivateProfileUserAvailable() {
- ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+ ResolverWrapperActivity.sOverrides.privateProfileUserHandle =
+ UserHandle.of(PRIVATE_USER_ID);
}
private void setTabOwnerUserHandleForLaunch(UserHandle tabOwnerUserHandleForLaunch) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 90f5c24e13a1..cdf506c941e9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -22,10 +22,10 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.intThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 4604b01d1bd2..050a68a89d6f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -112,8 +112,8 @@ public class ResolverWrapperActivity extends ResolverActivity {
@Override
protected ResolverListController createListController(UserHandle userHandle) {
- if (userHandle == UserHandle.SYSTEM) {
- when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ if (userHandle == getUser()) {
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(getUser());
return sOverrides.resolverListController;
}
if (isLaunchedInSingleUserMode()) {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 17fe15c94294..21ef391ee9de 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -28,7 +28,7 @@ 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.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.annotation.EnforcePermission;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
@@ -207,7 +207,7 @@ public final class DeviceStateManagerGlobalTest {
mService.setSupportedStates(List.of(OTHER_DEVICE_STATE));
mService.setBaseState(OTHER_DEVICE_STATE);
- verifyZeroInteractions(callback);
+ verifyNoMoreInteractions(callback);
}
@Test
diff --git a/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java b/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java
index 8de919681006..7c6046223698 100644
--- a/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java
+++ b/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java
@@ -22,10 +22,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.contains;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.matches;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/core/tests/vibrator/src/android/os/ExternalVibrationTest.java b/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
index 8741907cd5ea..9c9d50202486 100644
--- a/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
+++ b/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
@@ -49,4 +49,22 @@ public class ExternalVibrationTest {
assertThat(restored.getAudioAttributes()).isEqualTo(original.getAudioAttributes());
assertThat(restored.getToken()).isEqualTo(original.getToken());
}
+
+ @Test
+ public void testSerialization_systemUsage() {
+ ExternalVibration original =
+ new ExternalVibration(
+ 123,
+ "pkg",
+ new AudioAttributes.Builder()
+ .setSystemUsage(AudioAttributes.USAGE_SPEAKER_CLEANUP)
+ .build(),
+ IExternalVibrationController.Stub.asInterface(new Binder()));
+ Parcel p = Parcel.obtain();
+ original.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExternalVibration restored = ExternalVibration.CREATOR.createFromParcel(p);
+
+ assertThat(restored.getAudioAttributes()).isEqualTo(original.getAudioAttributes());
+ }
}
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 e141f70d8abb..da25da1256b0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -54,6 +54,8 @@ import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams;
+import static com.android.window.flags.Flags.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch;
+
import android.annotation.CallbackExecutor;
import android.app.Activity;
import android.app.ActivityClient;
@@ -815,11 +817,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
.setOriginType(TASK_FRAGMENT_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(TASK_FRAGMENT_TRANSIT_CLOSE);
- mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
+ if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()
+ && container.hasActivityLaunchHint()) {
+ // If we have recently attempted to launch a new activity into this
+ // TaskFragment, we schedule delayed cleanup. If the new activity appears in
+ // this TaskFragment, we no longer need to finish the TaskFragment.
+ container.scheduleDelayedTaskFragmentCleanup();
+ } else {
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
+ mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
+ }
}
} else if (wasInPip && isInPip) {
// No update until exit PIP.
@@ -3164,6 +3172,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// TODO(b/229680885): skip override launching TaskFragment token by split-rule
options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
+ if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) {
+ launchedInTaskFragment.setActivityLaunchHint();
+ }
mCurrentIntent = intent;
} else {
transactionRecord.abort();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index b3e003e7ad95..6fa855e8b6d6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -18,6 +18,8 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.window.flags.Flags.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch;
+
import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration.WindowingMode;
@@ -53,6 +55,8 @@ import java.util.Objects;
class TaskFragmentContainer {
private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
+ private static final int DELAYED_TASK_FRAGMENT_CLEANUP_TIMEOUT_MS = 500;
+
/** Parcelable data of this TaskFragmentContainer. */
@NonNull
private final ParcelableTaskFragmentContainerData mParcelableData;
@@ -165,6 +169,18 @@ class TaskFragmentContainer {
*/
private boolean mLastDimOnTask;
+ /** The timestamp of the latest pending activity launch attempt. 0 means no pending launch. */
+ private long mLastActivityLaunchTimestampMs = 0;
+
+ /**
+ * The scheduled runnable for delayed TaskFragment cleanup. This is used when the TaskFragment
+ * becomes empty, but we expect a new activity to appear in it soon.
+ *
+ * It should be {@code null} when not scheduled.
+ */
+ @Nullable
+ private Runnable mDelayedTaskFragmentCleanupRunnable;
+
/**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
@@ -540,6 +556,10 @@ class TaskFragmentContainer {
mAppearEmptyTimeout = null;
}
+ if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) {
+ clearActivityLaunchHintIfNecessary(mInfo, info);
+ }
+
mHasCrossProcessActivities = false;
mInfo = info;
if (mInfo == null || mInfo.isEmpty()) {
@@ -1064,6 +1084,89 @@ class TaskFragmentContainer {
return isOverlay() && mParcelableData.mAssociatedActivityToken != null;
}
+ /**
+ * Indicates whether there is possibly a pending activity launching into this TaskFragment.
+ *
+ * This should only be used as a hint because we cannot reliably determine if the new activity
+ * is going to appear into this TaskFragment.
+ *
+ * TODO(b/293800510) improve activity launch tracking in TaskFragment.
+ */
+ boolean hasActivityLaunchHint() {
+ if (mLastActivityLaunchTimestampMs == 0) {
+ return false;
+ }
+ if (System.currentTimeMillis() > mLastActivityLaunchTimestampMs + APPEAR_EMPTY_TIMEOUT_MS) {
+ // The hint has expired after APPEAR_EMPTY_TIMEOUT_MS.
+ mLastActivityLaunchTimestampMs = 0;
+ return false;
+ }
+ return true;
+ }
+
+ /** Records the latest activity launch attempt. */
+ void setActivityLaunchHint() {
+ mLastActivityLaunchTimestampMs = System.currentTimeMillis();
+ }
+
+ /**
+ * If we get a new info showing that the TaskFragment has more activities than the previous
+ * info, we clear the new activity launch hint.
+ *
+ * Note that this is not a reliable way and cannot cover situations when the attempted
+ * activity launch did not cause TaskFragment info activity count changes, such as trampoline
+ * launches or single top launches.
+ *
+ * TODO(b/293800510) improve activity launch tracking in TaskFragment.
+ */
+ private void clearActivityLaunchHintIfNecessary(
+ @Nullable TaskFragmentInfo oldInfo, @NonNull TaskFragmentInfo newInfo) {
+ final int previousActivityCount = oldInfo == null ? 0 : oldInfo.getRunningActivityCount();
+ if (newInfo.getRunningActivityCount() > previousActivityCount) {
+ mLastActivityLaunchTimestampMs = 0;
+ cancelDelayedTaskFragmentCleanup();
+ }
+ }
+
+ /**
+ * Schedules delayed TaskFragment cleanup due to pending activity launch. The scheduled cleanup
+ * will be canceled if a new activity appears in this TaskFragment.
+ */
+ void scheduleDelayedTaskFragmentCleanup() {
+ if (mDelayedTaskFragmentCleanupRunnable != null) {
+ // Remove the previous callback if there is already one scheduled.
+ mController.getHandler().removeCallbacks(mDelayedTaskFragmentCleanupRunnable);
+ }
+ mDelayedTaskFragmentCleanupRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mController.mLock) {
+ if (mDelayedTaskFragmentCleanupRunnable != this) {
+ // The scheduled cleanup runnable has been canceled or rescheduled, so
+ // skipping.
+ return;
+ }
+ if (isEmpty()) {
+ mLastActivityLaunchTimestampMs = 0;
+ mController.onTaskFragmentAppearEmptyTimeout(
+ TaskFragmentContainer.this);
+ }
+ mDelayedTaskFragmentCleanupRunnable = null;
+ }
+ }
+ };
+ mController.getHandler().postDelayed(
+ mDelayedTaskFragmentCleanupRunnable, DELAYED_TASK_FRAGMENT_CLEANUP_TIMEOUT_MS);
+ }
+
+ private void cancelDelayedTaskFragmentCleanup() {
+ if (mDelayedTaskFragmentCleanupRunnable == null) {
+ return;
+ }
+ mController.getHandler().removeCallbacks(mDelayedTaskFragmentCleanupRunnable);
+ mDelayedTaskFragmentCleanupRunnable = null;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
diff --git a/libs/WindowManager/Shell/aconfig/OWNERS b/libs/WindowManager/Shell/aconfig/OWNERS
index 9eba0f2dea7b..eacadeacab36 100644
--- a/libs/WindowManager/Shell/aconfig/OWNERS
+++ b/libs/WindowManager/Shell/aconfig/OWNERS
@@ -1,3 +1,4 @@
# Owners for flag changes
madym@google.com
-hwwang@google.com \ No newline at end of file
+hwwang@google.com
+sqsun@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 1e72d64397d7..b6a1501831c0 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -114,7 +114,7 @@ flag {
name: "enable_shell_top_task_tracking"
namespace: "multitasking"
description: "Enables tracking top tasks from the shell"
- bug: "342627272"
+ bug: "346588978"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -194,3 +194,20 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_gsf"
+ namespace: "multitasking"
+ description: "Applies GSF font styles to multitasking."
+ bug: "400534660"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_magnetic_split_divider"
+ namespace: "multitasking"
+ description: "Makes the split divider snap 'magnetically' to available snap points during drag"
+ bug: "383631946"
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt
index 24f43d347163..5777cb0cc8c5 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt
@@ -94,6 +94,7 @@ class DragZoneFactoryScreenshotTest(private val param: Param) {
private val splitScreenModeName =
when (splitScreenMode) {
+ SplitScreenMode.UNSUPPORTED -> "_split_unsupported"
SplitScreenMode.NONE -> ""
SplitScreenMode.SPLIT_50_50 -> "_split_50_50"
SplitScreenMode.SPLIT_10_90 -> "_split_10_90"
diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
index 03076c0940a4..50666911e313 100644
--- a/libs/WindowManager/Shell/multivalentTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -51,6 +51,7 @@ android_robolectric_test {
"androidx.test.ext.junit",
"mockito-robolectric-prebuilt",
"mockito-kotlin2",
+ "platform-parametric-runner-lib",
"truth",
"flag-junit-base",
"flag-junit",
@@ -74,6 +75,7 @@ android_test {
"frameworks-base-testutils",
"mockito-kotlin2",
"mockito-target-extended-minus-junit4",
+ "platform-parametric-runner-lib",
"truth",
"platform-test-annotations",
"platform-test-rules",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt
new file mode 100644
index 000000000000..bdfaef2c6960
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.ComponentName
+import android.content.Context
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.Flags
+import com.android.wm.shell.taskview.TaskView
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+/** Tests for [BubbleExpandedView] */
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class BubbleExpandedViewTest(flags: FlagsParameterization) {
+
+ @get:Rule
+ val setFlagsRule = SetFlagsRule(flags)
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private val componentName = ComponentName(context, "TestClass")
+
+ @Test
+ fun getTaskId_onTaskCreated_returnsCorrectTaskId() {
+ val bubbleTaskView = BubbleTaskView(mock<TaskView>(), directExecutor())
+ val expandedView = BubbleExpandedView(context).apply {
+ initialize(
+ mock<BubbleExpandedViewManager>(),
+ mock<BubbleStackView>(),
+ mock<BubblePositioner>(),
+ false /* isOverflow */,
+ bubbleTaskView,
+ )
+ setAnimating(true) // Skips setContentVisibility for testing.
+ }
+
+ bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+ assertThat(expandedView.getTaskId()).isEqualTo(123)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams() = FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER,
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
index 9087da34d259..636ff669d6b4 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
@@ -266,8 +266,6 @@ class BubbleTaskViewListenerTest {
optionsCaptor.capture(),
any())
- assertThat((intentCaptor.lastValue.flags
- and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
@@ -295,8 +293,6 @@ class BubbleTaskViewListenerTest {
optionsCaptor.capture(),
any())
- assertThat((intentCaptor.lastValue.flags
- and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
@@ -324,8 +320,6 @@ class BubbleTaskViewListenerTest {
optionsCaptor.capture(),
any())
- assertThat((intentCaptor.lastValue.flags
- and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
index 2d6df43f67e0..3597ce0041d4 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/UiEventSubject.kt
@@ -22,14 +22,14 @@ import com.google.common.truth.Subject
import com.google.common.truth.Truth
/** Subclass of [Subject] to simplify verifying [FakeUiEvent] data */
-class UiEventSubject(metadata: FailureMetadata, private val actual: FakeUiEvent) :
+class UiEventSubject(metadata: FailureMetadata, private val actual: FakeUiEvent?) :
Subject(metadata, actual) {
/** Check that [FakeUiEvent] contains the expected data from the [bubble] passed id */
fun hasBubbleInfo(bubble: Bubble) {
- check("uid").that(actual.uid).isEqualTo(bubble.appUid)
- check("packageName").that(actual.packageName).isEqualTo(bubble.packageName)
- check("instanceId").that(actual.instanceId).isEqualTo(bubble.instanceId)
+ check("uid").that(actual?.uid).isEqualTo(bubble.appUid)
+ check("packageName").that(actual?.packageName).isEqualTo(bubble.packageName)
+ check("instanceId").that(actual?.instanceId).isEqualTo(bubble.instanceId)
}
companion object {
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml
index b35dc022e210..56e5dc717a7b 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml
@@ -18,9 +18,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportHeight="24"
- android:viewportWidth="24">
+ android:viewportWidth="960"
+ android:viewportHeight="960">
<path
android:fillColor="#FF000000"
- android:pathData="M6,21V19H18V21Z"/>
+ android:pathData="M160,800L160,720L800,720L800,800L160,800Z"/>
</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 4daaf9c6b57f..225303b2d942 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -103,4 +103,35 @@
</LinearLayout>
+ <!-- Menu option to move a bubble to fullscreen; only visible if bubble anything is enabled. -->
+ <LinearLayout
+ android:id="@+id/bubble_manage_menu_fullscreen_container"
+ android:background="@drawable/bubble_manage_menu_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:minHeight="@dimen/bubble_menu_item_height"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/bubble_manage_menu_fullscreen_icon"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
+ android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
+ android:tint="@color/bubbles_icon_tint"/>
+
+ <TextView
+ android:id="@+id/bubble_manage_menu_fullscreen_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:text="@string/bubble_fullscreen_text"
+ android:textColor="@androidprv:color/materialColorOnSurface"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+ </LinearLayout>
+
</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 477d207a5c7e..ed8b54397076 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -22,8 +22,8 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation"
- android:paddingEnd="@dimen/desktop_mode_handle_menu_pill_elevation"
+ android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation_padding"
+ android:paddingEnd="@dimen/desktop_mode_handle_menu_pill_elevation_padding"
android:orientation="vertical">
<LinearLayout
@@ -39,8 +39,8 @@
<ImageView
android:id="@+id/application_icon"
- android:layout_width="@dimen/desktop_mode_caption_icon_radius"
- android:layout_height="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_width="@dimen/desktop_mode_handle_menu_icon_radius"
+ android:layout_height="@dimen/desktop_mode_handle_menu_icon_radius"
android:layout_marginStart="10dp"
android:layout_marginEnd="12dp"
android:contentDescription="@string/app_icon_text"
@@ -53,15 +53,12 @@
<com.android.wm.shell.windowdecor.HandleMenuImageButton
android:id="@+id/collapse_menu_button"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginStart="14dp"
+ android:padding="16dp"
android:contentDescription="@string/collapse_menu_text"
- android:src="@drawable/ic_baseline_expand_more_24"
+ android:src="@drawable/ic_baseline_expand_more_16"
android:rotation="180"
android:tint="@androidprv:color/materialColorOnSurface"
- android:background="?android:selectableItemBackgroundBorderless"/>
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
</LinearLayout>
<LinearLayout
@@ -145,6 +142,7 @@
android:contentDescription="@string/screenshot_text"
android:text="@string/screenshot_text"
android:src="@drawable/desktop_mode_ic_handle_menu_screenshot"
+ android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
<com.android.wm.shell.windowdecor.HandleMenuActionButton
@@ -152,6 +150,7 @@
android:contentDescription="@string/new_window_text"
android:text="@string/new_window_text"
android:src="@drawable/desktop_mode_ic_handle_menu_new_window"
+ android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
<com.android.wm.shell.windowdecor.HandleMenuActionButton
@@ -159,6 +158,7 @@
android:contentDescription="@string/manage_windows_text"
android:text="@string/manage_windows_text"
android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows"
+ android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
<com.android.wm.shell.windowdecor.HandleMenuActionButton
@@ -166,6 +166,7 @@
android:contentDescription="@string/change_aspect_ratio_text"
android:text="@string/change_aspect_ratio_text"
android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+ android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
@@ -185,6 +186,7 @@
android:text="@string/open_in_browser_text"
android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
style="@style/DesktopModeHandleMenuActionButton"
+ android:importantForAccessibility="no"
android:layout_width="0dp"
android:layout_weight="1"/>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
index 0163c018479f..de38e6fc2330 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
@@ -34,5 +34,6 @@
<com.android.wm.shell.windowdecor.MarqueedTextView
android:id="@+id/label"
+ android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButtonTextView"/>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index c2aa146d6437..3ebe87dcf505 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -38,11 +38,11 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp">
+ android:gravity="center_horizontal"
+ android:layout_marginHorizontal="4dp">
<Button
- android:layout_width="94dp"
+ android:layout_width="108dp"
android:layout_height="60dp"
android:id="@+id/maximize_menu_immersive_toggle_button"
style="?android:attr/buttonBarButtonStyle"
@@ -75,8 +75,7 @@
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_horizontal"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp">
+ android:layout_marginHorizontal="4dp">
<Button
android:layout_width="108dp"
@@ -112,8 +111,7 @@
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_horizontal"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp">
+ android:layout_marginHorizontal="4dp">
<LinearLayout
android:id="@+id/maximize_menu_snap_menu_layout"
android:layout_width="wrap_content"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 4dffce59aec6..1491c70023e7 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Links 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Links 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Volskerm regs"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Ruil boonste app met onderste een"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Ruil linkerapp met regterapp"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Volskerm bo"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Bo 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bo 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Die appkieslys kan hier gevind word"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Maak rekenaaraansig oop om veelvuldige apps saam oop te maak"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Keer enige tyd terug na volskerm vanaf die appkieslys"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\nhierdie app te skuif"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimeer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Stel <xliff:g id="APP_NAME">%1$s</xliff:g> terug"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimeer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Maak <xliff:g id="APP_NAME">%1$s</xliff:g> toe"</string>
<string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
<string name="handle_text" msgid="4419667835599523257">"Apphandvatsel"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Appikoon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Volskerm"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Rekenaaraansig"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Verdeelde skerm"</string>
<string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
<string name="float_button_text" msgid="9221657008391364581">"Sweef"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Verander aspekverhouding"</string>
<string name="close_text" msgid="4986518933445178928">"Maak toe"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Rekenaaraansig)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Verander grootte"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Verander grootte van linkerkantse venster"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Verander grootte van regterkantse venster"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimeer of stel venstergrootte terug"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimeer appvenstergrootte"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Stel venstergrootte terug"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimeer appvenster"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Maak appvenster toe"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Maak By Verstek Oop-instellings"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Kies hoe om webskakels vir hierdie app oop te maak"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In die app"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 0881e778fa52..8bd602c08508 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ግራ 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ግራ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገፅ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ከላይ ያለውን መተግበሪያ ከታች ባለው ቀይር"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"በግራ ያለውን መተግበሪያን በቀኝ ባለው ቀይር"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገፅ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ከላይ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ከላይ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"የመተግበሪያ ምናሌው እዚህ መገኘት ይችላል"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"በርካታ መተግበሪያዎችን በአንድ ላይ ለመክፈት ወደ የዴስክቶፕ እይታ ይግቡ"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"በማንኛውም ጊዜ ከመተግበሪያ ምናሌው ላይ ወደ ሙሉ ገጽ እይታ ይመለሱ"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገፅ ሌላ መተግበሪያ ይጎትቱ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"እንደገና ያስጀምሩ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ዳግም አታሳይ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ይህን መተግበሪያ\nለማንቀሳቀስ ሁለቴ መታ ያድርጉ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን አሳድግ"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ወደነበረበት መልስ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን አሳንስ"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ዝጋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
<string name="handle_text" msgid="4419667835599523257">"የመተግበሪያ መያዣ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"የመተግበሪያ አዶ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"የዴስክቶፕ ዕይታ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገፅ"</string>
<string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ምጥጥነ ገፅታ ለውጥ"</string>
<string name="close_text" msgid="4986518933445178928">"ዝጋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> የዴስክቶፕ ዕይታ"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"መጠን ቀይር"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"መስኮትን ወደ ግራ መጠን ቀይር"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"መስኮትን ወደ ቀኝ መጠን ቀይር"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"የመስኮት መጠንን አሳድግ ወይም ወደነበረበት መልስ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"የመተግበሪያ መስኮት መጠንን አሳድግ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"የመስኮት መጠንን ወደነበረበት መልስ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"የመተግበሪያ መስኮትን አሳንስ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"የመተግበሪያ መስኮትን ዝጋ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"በነባሪ ቅንብሮች ክፈት"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ለዚህ የድር መተግበሪያ አገናኙን እንዴት እንደሚከፍቱ ይምረጡ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"በመተግበሪያው ውስጥ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 9cc49aa144b5..70a23b73b6f5 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -43,10 +43,8 @@
<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>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"تبديل التطبيق العلوي بالسفلي"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"تبديل التطبيق الأيسر بالأيمن"</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>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"يمكن العثور على قائمة التطبيقات هنا"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"يمكنك الدخول إلى وضع العرض المخصّص للكمبيوتر المكتبي لفتح عدة تطبيقات معًا"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"يمكنك الرجوع إلى وضع ملء الشاشة في أي وقت من قائمة التطبيقات"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"إعادة التشغيل"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"عدم عرض مربّع حوار التأكيد مجددًا"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"انقر مرّتَين لنقل\nهذا التطبيق."</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"تكبير \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"استعادة \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"تصغير \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="close_button_text" msgid="4544839489310949894">"إغلاق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
<string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
<string name="handle_text" msgid="4419667835599523257">"مقبض التطبيق"</string>
<string name="app_icon_text" msgid="2823268023931811747">"رمز التطبيق"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string>
- <string name="desktop_text" msgid="1582173066857454541">"العرض المخصّص للكمبيوتر المكتبي"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string>
<string name="more_button_text" msgid="3655388105592893530">"المزيد"</string>
<string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"تغيير نسبة العرض إلى الارتفاع"</string>
<string name="close_text" msgid="4986518933445178928">"إغلاق"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> (العرض المخصّص للكمبيوتر المكتبي)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغيير الحجم"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"تغيير حجم النافذة بمحاذاتها إلى اليمين"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"تغيير حجم النافذة بمحاذاتها إلى اليسار"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"تكبير حجم النافذة أو استعادته"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"تكبير نافذة التطبيق"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"استعادة حجم النافذة"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"تصغير نافذة التطبيق"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"إغلاق نافذة التطبيق"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"إعدادات الفتح تلقائيًا"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"اختيار طريقة فتح روابط الويب لهذا التطبيق"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"في التطبيق"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index c59753c7803f..6872df6acbc9 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -43,10 +43,8 @@
<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>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"একেবাৰে তলৰ সৈতে একেবাৰে ওপৰৰ এপ্‌টো সলনাসলনি কৰক"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"সোঁফালৰ সৈতে বাওঁফালৰ এপ্‌টো সলনাসলনি কৰক"</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>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"এপৰ মেনু ইয়াত বিচাৰি পোৱা যাব"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"একেলগে একাধিক এপ্‌ খুলিবলৈ ডেস্কটপ ভিউলৈ যাওক"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"এপৰ মেনুৰ পৰা যিকোনো সময়তে পূৰ্ণ স্ক্ৰীনলৈ উভতি যাওক"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ৰিষ্টাৰ্ট কৰক"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"পুনৰাই নেদেখুৱাব"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই এপ্‌টো\nস্থানান্তৰ কৰিবলৈ দুবাৰ টিপক"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> মেক্সিমাইজ কৰক"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক পুনঃস্থাপন কৰক"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> মিনিমাইজ কৰক"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ কৰক"</string>
<string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
<string name="handle_text" msgid="4419667835599523257">"এপৰ হেণ্ডেল"</string>
<string name="app_icon_text" msgid="2823268023931811747">"এপৰ চিহ্ন"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ডেস্কটপ ভিউ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string>
<string name="more_button_text" msgid="3655388105592893530">"অধিক"</string>
<string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"আকাৰৰ অনুপাত সলনি কৰক"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ডেস্কটপ ভিউ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"আকাৰ সলনি কৰক"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্‌টো আনিব নোৱাৰি"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"সোঁফাললৈ ৱিণ্ড’ৰ আকাৰ সলনি কৰক"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"বাওঁফাললৈ ৱিণ্ড’ৰ আকাৰ সলনি কৰক"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ৱিণ্ড’ৰ আকাৰ মেক্সিমাইজ বা পুনঃস্থাপন কৰক"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"এপ্‌ ৱিণ্ড’ৰ আকাৰ মেক্সিমাইজ কৰক"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ৱিণ্ড’ৰ আকাৰ পুনঃস্থাপন কৰক"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"এপ্‌ ৱিণ্ড’ মিনিমাইজ কৰক"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"এপ্‌ ৱিণ্ড’ বন্ধ কৰক"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ডিফ’ল্ট ছেটিং খোলক"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"এই এপ্‌টোৰ বাবে কিদৰে ৱেব লিংক খুলিব পাৰি সেয়া বাছনি কৰক"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"এপ্‌টোত"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 63e610b53420..46309411d0c9 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sol 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Sol 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Sağ tam ekran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Yuxarıdakı tətbiqi aşağıdakı ilə dəyişdirin"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Soldakı tətbiqi sağdakı ilə dəyişdirin"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Yuxarı tam ekran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Yuxarı 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yuxarı 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Tətbiq menyusunu burada tapa bilərsiniz"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Bir neçə tətbiqi birlikdə açmaq üçün masaüstü görünüşə daxil olun"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"İstənilən vaxt tətbiq menyusundan tam ekrana qayıdın"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran üçün başqa tətbiq sürüşdürün"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tətbiqi köçürmək üçün\niki dəfə toxunun"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Böyüdün: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Bərpa edin: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Kiçildin: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Bağlayın: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
<string name="handle_text" msgid="4419667835599523257">"Tətbiq ləqəbi"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Tətbiq ikonası"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Masaüstü Görünüş"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
<string name="more_button_text" msgid="3655388105592893530">"Ardı"</string>
<string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tərəflər nisbətini dəyişin"</string>
<string name="close_text" msgid="4986518933445178928">"Bağlayın"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Masaüstü Görünüş)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ölçüsünü dəyişin"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Pəncərə ölçüsünü sola dəyişin"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Pəncərə ölçüsünü sağa dəyişin"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Pəncərə ölçüsünü artırın və ya bərpa edin"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Tətbiq pəncərəsi ölçüsünü böyüdün"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pəncərə ölçüsünü bərpa edin"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Tətbiq pəncərəsini kiçildin"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tətbiq pəncərəsini bağlayın"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Defolt ayarlarla açın"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Bu tətbiq üçün veb-linklərin necə açılacağını seçin"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Tətbiqdə"</string>
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 7cb6f87dc97f..4af564833ba1 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi ekran 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Levi ekran 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Režim celog ekrana za donji ekran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamenite gornju aplikaciju donjom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamenite levu aplikaciju desnom"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Režim celog ekrana za gornji ekran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gornji ekran 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji ekran 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Meni aplikacije možete da pronađete ovde"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Uđite u prikaz za računare da biste istovremeno otvorili više aplikacija"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Vratite se na ceo ekran bilo kada iz menija aplikacije"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremestili ovu aplikaciju"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Uvećajte: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Vratite: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Umanjite: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zatvorite: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="4419667835599523257">"Identifikator aplikacije"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Prikaz za računare"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string>
<string name="more_button_text" msgid="3655388105592893530">"Još"</string>
<string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promeni razmeru"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (prikaz za računare)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Prilagodi"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Promenite veličinu prozora nalevo"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Promenite veličinu prozora nadesno"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Uvećajte ili vratite veličinu prozora"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Uvećajte veličinu prozora aplikacije"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vratite veličinu prozora"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Umanjite prozor aplikacije"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zatvorite prozor aplikacije"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Podešavanje Podrazumevano otvaraj"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Odaberite način otvaranja veb-linkova za ovu aplikaciju"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 4f3da2b2f5d8..7719396b01d1 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левы экран – 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Левы экран – 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Правы экран – поўнаэкранны рэжым"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Памяняць месцамі верхнюю і ніжнюю праграмы"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Памяняць месцамі левую і правую праграмы"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Верхні экран – поўнаэкранны рэжым"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Верхні экран – 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхні экран – 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Меню праграмы шукайце тут"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Каб адкрыць некалькі праграм адначасова, увайдзіце ў версію для камп’ютараў"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Вы можаце вярнуцца ў поўнаэкранны рэжым у любы час з меню праграмы"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Перазапусціць"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больш не паказваць"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Каб перамясціць праграму,\nнацісніце двойчы"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Разгарнуць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Аднавіць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Згарнуць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Закрыць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Маркер праграмы"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Значок праграмы"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Версія для камп’ютараў"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string>
<string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string>
<string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Змяніць суадносіны бакоў"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыць"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (версія для камп’ютараў)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змяніць памер"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Змяніць памер акна і перамясціць да левага краю"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Змяніць памер акна і перамясціць да правага краю"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Разгарнуць акно ці аднавіць яго памер"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Разгарнуць акно праграмы"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Аднавіць памер акна"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Згарнуць акно праграмы"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Закрыць акно праграмы"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Налады параметра \"Адкрываць стандартна\""</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Выберыце, як гэта праграма будзе адкрываць вэб-спасылкі"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У праграме"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 3f867a22e13b..514556e30fe0 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ляв екран: 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ляв екран: 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Десен екран: Показване на цял екран"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Размяна на горното и долното приложение"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Размяна на лявото и дясното приложение"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Горен екран: Показване на цял екран"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Горен екран: 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горен екран: 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Можете да намерите менюто на приложението тук"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Активирайте изгледа за настолни компютри, за да отворите няколко приложения едновременно"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Преминете към цял екран по всяко време от менюто на приложението"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартиране"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Да не се показва отново"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Докоснете двукратно, за да\nпреместите това приложение"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Увеличаване на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Възстановяване на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Намаляване на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Затваряне на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Манипулатор за приложението"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Икона на приложението"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Изглед за настолни компютри"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string>
<string name="more_button_text" msgid="3655388105592893530">"Още"</string>
<string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промяна на съотношението"</string>
<string name="close_text" msgid="4986518933445178928">"Затваряне"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (изглед за настолни компютри)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Нов размер"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Преоразмеряване на прозореца наляво"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Преоразмеряване на прозореца надясно"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Увеличаване или възстановяване на размера на прозореца"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Увеличаване на размера на прозореца на приложението"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Възстановяване на размера на прозореца"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Намаляване на прозореца на приложението"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Затваряне на прозореца на приложението"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Отваряне на настройките по подразбиране"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Изберете как да се отварят уеб връзките за това приложение"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложението"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 3967d4bfa591..d4488a220ac7 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -43,10 +43,8 @@
<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>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"নিচেরটির মাধ্যমে উপরের অ্যাপ অদল বদল করে নিন"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ডানদিকের মাধ্যমে বাঁদিকের অ্যাপ অদল বদল করে নিন"</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>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"অ্যাপ মেনু এখানে খুঁজে পাওয়া যাবে"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"একসাথে একাধিক অ্যাপ খোলার জন্য ডেস্কটপ ভিউতে যান"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"অ্যাপ মেনু থেকে ফুল-স্ক্রিন মোডে যেকোনও সময়ে ফিরে আসুন"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"স্প্লিট স্ক্রিনের ক্ষেত্রে অন্য কোনও অ্যাপ টেনে আনুন"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"রিস্টার্ট করুন"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"আর দেখতে চাই না"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই অ্যাপ সরাতে\nডবল ট্যাপ করুন"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> বড় করুন"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> আবার ফিরিয়ে আনুন"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ছোট করুন"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ করুন"</string>
<string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
<string name="handle_text" msgid="4419667835599523257">"অ্যাপের হ্যান্ডেল"</string>
<string name="app_icon_text" msgid="2823268023931811747">"অ্যাপ আইকন"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ডেস্কটপ ভিউ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string>
<string name="more_button_text" msgid="3655388105592893530">"আরও"</string>
<string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ডেস্কটপ ভিউ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ছোট বড় করুন"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"বাঁদিকে উইন্ডো রিসাইজ করুন"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ডানদিকে উইন্ডো রিসাইজ করুন"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"উইন্ডো সাইজ বড় বা রিস্টোর করুন"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"অ্যাপ উইন্ডোর সাইজ বাড়ান"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"উইন্ডোর সাইজ ফিরিয়ে আনুন"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"অ্যাপ উইন্ডো ছোট করুন"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"অ্যাপ উইন্ডো বন্ধ করুন"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ডিফল্ট হিসেবে থাকা সেটিংস খুলুন"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"এই অ্যাপের জন্য কীভাবে ওয়েব লিঙ্ক খুলবেন তা বেছে নিন"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"অ্যাপের মধ্যে"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 6b59d91e741f..23c467c0b4ba 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevo 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Lijevo 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Desno cijeli ekran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamjena gornje aplikacije donjom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamjena lijeve aplikacije desnom"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Gore cijeli ekran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gore 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gore 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Ovdje možete pronaći meni aplikacije"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Ulazak u prikaz na računaru radi istovremenog otvaranja više aplikacija"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Povratak na prikaz preko cijelog ekrana bilo kada putem menija aplikacije"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dodirnite dvaput da\npomjerite aplikaciju"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimiziranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Vraćanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimiziranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zatvaranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="4419667835599523257">"Ručica aplikacije"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Prikaz na računaru"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string>
<string name="more_button_text" msgid="3655388105592893530">"Više"</string>
<string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promjena formata slike"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (prikaz na računaru)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Promjena veličine prozora i poravnanje lijevo"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Promjena veličine prozora i poravnanje desno"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimiziranje ili vraćanje veličine prozora"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimiziranje veličine prozora aplikacije"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vraćanje veličine prozora"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimiziranje prozora aplikacije"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zatvaranje prozora aplikacije"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Otvaranje prema zadanim postavkama"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Odaberite način otvaranja web linkova za ovu aplikaciju"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 955e5cc6ecbc..893d16e6155e 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pantalla esquerra al 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Pantalla esquerra al 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla dreta completa"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Intercanvia l\'aplicació superior amb la inferior"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Intercanvia l\'aplicació esquerra amb la dreta"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla superior completa"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Pantalla superior al 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Pantalla superior al 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Pots trobar el menú de l\'aplicació aquí"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Accedeix a la visualització per a ordinadors per obrir diverses aplicacions alhora"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Torna a la pantalla completa en qualsevol moment des del menú de l\'aplicació"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Fes doble toc per\nmoure aquesta aplicació"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximitza <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaura <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimitza <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Tanca <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
<string name="handle_text" msgid="4419667835599523257">"Identificador de l\'aplicació"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icona de l\'aplicació"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Visualització per a ordinadors"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
<string name="more_button_text" msgid="3655388105592893530">"Més"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flotant"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Canvia la relació d\'aspecte"</string>
<string name="close_text" msgid="4986518933445178928">"Tanca"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (visualització per a ordinadors)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Canvia la mida"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Canvia la mida de la finestra a l\'esquerra"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Canvia la mida de la finestra a la dreta"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximitza o restaura la mida de la finestra"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximitza la mida de la finestra de l\'aplicació"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaura la mida de la finestra"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimitza la finestra de l\'aplicació"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tanca la finestra de l\'aplicació"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Configuració d\'obertura predeterminada"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Tria com vols obrir els enllaços web per a aquesta aplicació"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"A l\'aplicació"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 673f7fc2f8c1..a96344a0a365 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % vlevo"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % vlevo"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pravá část na celou obrazovku"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Prohodit horní a dolní aplikaci"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Prohodit levou a pravou aplikaci"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Horní část na celou obrazovku"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % nahoře"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % nahoře"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Najdete tu nabídku aplikace"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Pokud chcete otevřít několik aplikací současně, přejděte na zobrazení na počítači"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Na celou obrazovku se můžete kdykoli vrátit z nabídky aplikace"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvojitým klepnutím\npřesunete aplikaci"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximalizovat aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Obnovit aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimalizovat aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zavřít aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
<string name="handle_text" msgid="4419667835599523257">"Popisovač aplikace"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikace"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Zobrazení na počítači"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string>
<string name="more_button_text" msgid="3655388105592893530">"Více"</string>
<string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Změnit poměr stran"</string>
<string name="close_text" msgid="4986518933445178928">"Zavřít"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (zobrazení na počítači)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Změnit velikost"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Přichytit okno vlevo"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Přichytit okno vpravo"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximalizovat nebo obnovit velikost okna"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximalizovat velikost okna aplikace"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Obnovit velikost okna"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalizovat okno aplikace"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zavřít okno aplikace"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Otevírat podle výchozího nastavení"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Určete, jak se v této aplikaci mají otevírat webové odkazy"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaci"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 635df334f9ff..7d28fc842e59 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Venstre 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Venstre 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Vis højre del i fuld skærm"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Byt om på den øverste og nederste app"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Byt om på den venstre og højre app"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Vis øverste del i fuld skærm"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Øverste 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Øverste 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Appmenuen kan findes her"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Gå til computervenlig visning for at åbne flere apps på én gang"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Gå tilbage til fuld skærm når som helst via appmenuen"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Træk en anden app hertil for at bruge opdelt skærm"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryk to gange\nfor at flytte appen"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Gendan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Luk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
<string name="handle_text" msgid="4419667835599523257">"Apphåndtag"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Computervenlig visning"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mere"</string>
<string name="float_button_text" msgid="9221657008391364581">"Svævende"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Skift billedformat"</string>
<string name="close_text" msgid="4986518933445178928">"Luk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (computervenlig visning)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Tilpas størrelse"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Juster størrelsen på vinduet til venstre"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Juster størrelsen på vinduet til højre"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimer eller gendan vinduesstørrelse"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimer størrelsen på appvinduet"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Gendan størrelsen på vinduet"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimer appvindue"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Luk appvindue"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Indstillinger for automatisk åbning"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Vælg, hvordan denne app skal åben weblinks"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 5be8b0b34993..4cc11243b8bd 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % links"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % links"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Vollbild rechts"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Obere und untere App vertauschen"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Linke und rechte App vertauschen"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Vollbild oben"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % oben"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % oben"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Das App-Menü findest du hier"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Über die Desktop-Ansicht kannst du mehrere Apps gleichzeitig öffnen"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Über das App-Menü kannst du jederzeit zum Vollbildmodus zurückkehren"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Für Splitscreen-Modus weitere App hineinziehen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Zum Verschieben\ndoppeltippen"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> maximieren"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> wiederherstellen"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> minimieren"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> schließen"</string>
<string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
<string name="handle_text" msgid="4419667835599523257">"App-Ziehpunkt"</string>
<string name="app_icon_text" msgid="2823268023931811747">"App-Symbol"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop-Ansicht"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Splitscreen"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
<string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Seitenverhältnis ändern"</string>
<string name="close_text" msgid="4986518933445178928">"Schließen"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Desktop-Ansicht)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Größe ändern"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Fenstergröße nach links anpassen"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Fenstergröße nach rechts anpassen"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Fenstergröße maximieren oder wiederherstellen"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"App-Fenster maximieren"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Fenstergröße wiederherstellen"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"App-Fenster minimieren"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"App-Fenster schließen"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Einstellungen für die Option „Standardmäßig öffnen“"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Festlegen, wie Weblinks für diese App geöffnet werden"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In der App"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index bd3cf053836a..0fb17ecdb278 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Αριστερή 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Αριστερή 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Δεξιά πλήρης οθόνη"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Εναλλαγή της εφαρμογής στην κορυφή με αυτή στο κάτω μέρος"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Εναλλαγή της εφαρμογής στα αριστερά με αυτή στα δεξιά"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Πάνω πλήρης οθόνη"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Πάνω 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Πάνω 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Μπορείτε να βρείτε το μενού εφαρμογών εδώ"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Μεταβείτε στην προβολή για υπολογιστές, για να ανοίξετε πολλές εφαρμογές μαζί"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Επιστρέψτε στην πλήρη οθόνη ανά πάσα στιγμή από το μενού της εφαρμογής"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης."</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Πατήστε δύο φορές για\nμετακίνηση αυτής της εφαρμογής"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Μεγιστοποίηση <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Επαναφορά <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Ελαχιστοποίηση <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Κλείσιμο <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
<string name="handle_text" msgid="4419667835599523257">"Λαβή εφαρμογής"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Εικονίδιο εφαρμογής"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Προβολή για υπολογιστές"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string>
<string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string>
<string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Αλλαγή λόγου διαστάσεων"</string>
<string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Προβολή για υπολογιστές)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Αλλαγή μεγέθους"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Αλλαγή μεγέθους παραθύρου προς τα αριστερά"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Αλλαγή μεγέθους παραθύρου προς τα δεξιά"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Μεγιστοποίηση ή επαναφορά μεγέθους παραθύρου"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Μεγιστοποίηση μεγέθους παραθύρου εφαρμογής"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Επαναφορά μεγέθους παραθύρου"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Ελαχιστοποίηση παραθύρου εφαρμογής"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Κλείσιμο παραθύρου εφαρμογής"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Άνοιγμα ρυθμίσεων από προεπιλογή"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Επιλογή τρόπου ανοίγματος συνδέσμων ιστού για την εφαρμογή"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Στην εφαρμογή"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index b137d80dcd2b..2087bb4ad579 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Left 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Right full screen"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Swap top app with bottom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Swap left app with right"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Top full screen"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Top 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"The app menu can be found here"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Enter desktop view to open multiple apps together"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Return to full screen at any time from the app menu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restore <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Close <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="4419667835599523257">"App handle"</string>
<string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop view"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
<string name="more_button_text" msgid="3655388105592893530">"More"</string>
<string name="float_button_text" msgid="9221657008391364581">"Float"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (desktop view)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Resize window to left"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Resize window to right"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximise or restore window size"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximise app window size"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restore window size"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimise app window"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Close app window"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Open by default settings"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choose how to open web links for this app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 9b9294dbeba8..8abb9dffec67 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -100,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"The app menu can be found here"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Enter desktop view to open multiple apps together"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Return to full screen anytime from the app menu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
@@ -121,7 +122,8 @@
<string name="handle_text" msgid="4419667835599523257">"App handle"</string>
<string name="app_icon_text" msgid="2823268023931811747">"App Icon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop View"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
<string name="more_button_text" msgid="3655388105592893530">"More"</string>
<string name="float_button_text" msgid="9221657008391364581">"Float"</string>
@@ -134,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Desktop View)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index b137d80dcd2b..2087bb4ad579 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Left 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Right full screen"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Swap top app with bottom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Swap left app with right"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Top full screen"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Top 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"The app menu can be found here"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Enter desktop view to open multiple apps together"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Return to full screen at any time from the app menu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restore <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Close <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="4419667835599523257">"App handle"</string>
<string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop view"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
<string name="more_button_text" msgid="3655388105592893530">"More"</string>
<string name="float_button_text" msgid="9221657008391364581">"Float"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (desktop view)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Resize window to left"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Resize window to right"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximise or restore window size"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximise app window size"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restore window size"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimise app window"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Close app window"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Open by default settings"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choose how to open web links for this app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index b137d80dcd2b..2087bb4ad579 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Left 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Right full screen"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Swap top app with bottom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Swap left app with right"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Top full screen"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Top 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"The app menu can be found here"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Enter desktop view to open multiple apps together"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Return to full screen at any time from the app menu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restore <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Close <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="4419667835599523257">"App handle"</string>
<string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop view"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
<string name="more_button_text" msgid="3655388105592893530">"More"</string>
<string name="float_button_text" msgid="9221657008391364581">"Float"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (desktop view)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Resize window to left"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Resize window to right"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximise or restore window size"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximise app window size"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restore window size"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimise app window"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Close app window"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Open by default settings"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choose how to open web links for this app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index ebfdc6d0a780..bb70f6e419a5 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda: 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Izquierda: 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla derecha completa"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Intercambiar la app superior con la inferior"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Intercambiar la app de la izquierda con la de la derecha"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla superior completa"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Superior: 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior: 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"El menú de la app se encuentra aquí"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Entra a la vista de escritorio para abrir varias apps a la vez"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Regresa a pantalla completa en cualquier momento desde el menú de la app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra app para el modo de pantalla dividida"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Presiona dos veces\npara mover esta app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restablecer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Cerrar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="4419667835599523257">"Controlador de la app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ícono de la app"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Vista para computadoras de escritorio"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
<string name="more_button_text" msgid="3655388105592893530">"Más"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
@@ -140,9 +136,10 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar relación de aspecto"</string>
<string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (vista para computadoras de escritorio)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
- <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar el tamaño"</string>
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Dividir pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restablecer"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ajustar el tamaño de la ventana hacia la izquierda"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ajustar el tamaño de la ventana hacia la derecha"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar o restablecer el tamaño de la ventana"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar el tamaño de la ventana de la app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restablecer el tamaño de la ventana"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar ventana de la app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Cerrar ventana de la app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Abrir con la configuración predeterminada"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Elige cómo abrir vínculos web para esta app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la app"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index dfa7869434bc..a6595aa9292d 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Izquierda 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla derecha completa"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Intercambiar aplicación superior con inferior"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Intercambiar aplicación izquierda con derecha"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla superior completa"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Superior 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"El menú de la aplicación se encuentra aquí"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Entra en la vista para ordenador si quieres abrir varias aplicaciones a la vez"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Vuelve a la pantalla completa en cualquier momento desde el menú de aplicaciones"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra aplicación para activar la pantalla dividida"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dos veces para\nmover esta aplicación"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Cerrar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="4419667835599523257">"Controlador de la aplicación"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icono de la aplicación"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Vista para ordenadores"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
<string name="more_button_text" msgid="3655388105592893530">"Más"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar relación de aspecto"</string>
<string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (vista para ordenadores)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Dividir pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover aquí"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Cambiar tamaño de la ventana a la izquierda"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Cambiar tamaño de la ventana a la derecha"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar o restaurar tamaño de la ventana"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar tamaño de la ventana de la aplicación"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar tamaño de la ventana"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar ventana de la aplicación"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Cerrar ventana de la aplicación"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Abrir con los ajustes predeterminados"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Elige cómo quieres abrir los enlaces web de esta aplicación"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la aplicación"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 294b7fee9954..36086578dd4d 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasak: 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vasak: 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Parem täisekraan"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Ülemise ja alumise rakenduse vahetamine"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Vasaku ja parema rakenduse vahetamine"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ülemine täisekraan"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Ülemine: 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ülemine: 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Rakenduse menüü leiate siit"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Mitme rakenduse koos avamiseks avage arvutivaade"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Saate rakenduse menüüst igal ajal täisekraanile naasta"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Rakenduse teisaldamiseks\ntopeltpuudutage"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> maksimeerimine"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> taastamine"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> minimeerimine"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> sulgemine"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
<string name="handle_text" msgid="4419667835599523257">"Rakenduse element"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Rakenduse ikoon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Arvutivaade"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string>
<string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string>
<string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Kuvasuhte muutmine"</string>
<string name="close_text" msgid="4986518933445178928">"Sule"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (arvutivaade)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Suuruse muutmine"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Akna suuruse muutmine, vasakule"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Akna suuruse muutmine, paremale"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Akna suuruse maksimeerimine või taastamine"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Rakenduse akna suuruse maksimeerimine"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Akna suuruse taastamined"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Rakenduse akna minimeerimine"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Rakenduse akna sulgemine"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Avamisviisi vaikeseaded"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Valige, kuidas avada selle rakenduse puhul veebilinke"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Rakenduses"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index a8f92128a603..446839a8eb06 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ezarri ezkerraldea % 50en"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ezarri ezkerraldea % 30en"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Ezarri eskuinaldea pantaila osoan"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Aldatu goiko aplikazioa behekoagatik"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Aldatu ezkerreko aplikazioa eskuinekoagatik"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ezarri goialdea pantaila osoan"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Ezarri goialdea % 70en"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ezarri goialdea % 50en"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Aplikazioaren menua dago hemen"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Sartu ordenagailuetarako ikuspegian aplikazio bat baino gehiago batera irekitzeko"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Pantaila osoko modura itzultzeko, erabili aplikazioaren menua"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Pantaila zatitua ikusteko, arrastatu beste aplikazio bat"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Sakatu birritan\naplikazioa mugitzeko"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizatu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Leheneratu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizatu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Itxi <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
<string name="handle_text" msgid="4419667835599523257">"Aplikazioaren kontrol-puntua"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Ordenagailuetarako ikuspegia"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitzea"</string>
<string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
<string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Aldatu aspektu-erlazioa"</string>
<string name="close_text" msgid="4986518933445178928">"Itxi"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ordenagailuetarako ikuspegia)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Aldatu tamaina"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Aldatu leihoaren tamaina eta eraman ezkerrera"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Aldatu leihoaren tamaina eta eraman eskuinera"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizatu edo leheneratu leihoaren tamaina"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizatu aplikazioaren leihoaren tamaina"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Leheneratu leihoaren tamaina"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizatu aplikazioaren leihoa"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Itxi aplikazioaren leihoa"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Modu lehenetsian irekitzearen ezarpenak"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Aukeratu nola ireki sareko estekak aplikazio honetan"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Aplikazioan"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 59affd7887be..bf5c8f95cfb3 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -43,10 +43,8 @@
<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>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"جابه‌جا کردن برنامه بالا با پایین"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"جابه‌جا کردن برنامه چپ با راست"</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>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن تک‌ضرب بزنید"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن تک‌ضرب بزنید."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"منو برنامه را می‌توانید اینجا ببینید"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"برای باز کردن هم‌زمان چند برنامه، وارد نمای ویژه رایانه شوید"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"هروقت خواستید از منو برنامه به حالت تمام‌صفحه برگردید"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراه‌اندازی"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابه‌جا کردن این برنامه\nدو تک‌ضرب بزنید"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"بزرگ کردن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"بازیابی <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"کوچک کردن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"بستن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
<string name="handle_text" msgid="4419667835599523257">"دستگیره برنامه"</string>
<string name="app_icon_text" msgid="2823268023931811747">"نماد برنامه"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"تمام‌صفحه"</string>
- <string name="desktop_text" msgid="1582173066857454541">"نمای ویژه رایانه رومیزی"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string>
<string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string>
<string name="float_button_text" msgid="9221657008391364581">"شناور"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"تغییر نسبت ابعادی"</string>
<string name="close_text" msgid="4986518933445178928">"بستن"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> (نمای ویژه رایانه رومیزی)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغییر اندازه"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمی‌توان به اینجا منتقل کرد"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"تغییر اندازه پنجره به چپ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"تغییر اندازه پنجره به راست"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"بیشینه‌سازی یا بازیابی اندازه پنجره"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"بزرگ کردن اندازه پنجره برنامه"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"بازیابی اندازه پنجره"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"کوچک کردن پنجره برنامه"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"بستن پنجره برنامه"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"تنظیمات باز کردن به‌طور پیش‌فرض"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"انتخاب روش باز کردن پیوندهای وب مربوط به این برنامه"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"در برنامه"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index b1d8431b57d9..a32e4fab9d81 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasen 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vasen 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Oikea koko näytölle"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Vaihda ylä- ja alareunan sovellukset"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Vaihda vasen sovellus oikealla olevaan sovellukseen"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Yläosa koko näytölle"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Yläosa 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yläosa 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Sovellusvalikko löytyy täältä"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Siirry työpöytänäkymään, niin voit avata useita sovelluksia kerralla"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Voit palata koko näytön tilaan milloin tahansa sovellusvalikosta"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Kaksoisnapauta, jos\nhaluat siirtää sovellusta"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Suurenna <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Palauta <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Pienennä <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Sulje <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
<string name="handle_text" msgid="4419667835599523257">"Sovelluksen tunnus"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Sovelluskuvake"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Tietokonenäkymä"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string>
<string name="more_button_text" msgid="3655388105592893530">"Lisää"</string>
<string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Vaihda kuvasuhdetta"</string>
<string name="close_text" msgid="4986518933445178928">"Sulje"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (tietokonenäkymä)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Muuta kokoa"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Muuta vasemmanpuoleisen ikkunan kokoa"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Muuta vasemmanpuoleisen ikkunan kokoa"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Suurenna ikkuna tai palauta ikkunan koko"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Suurenna sovellusikkunan koko"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Palauta ikkunan koko"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Pienennä sovellusikkuna"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Sulje sovellusikkuna"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Avaa oletusasetusten mukaan"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Valitse, miten verkkolinkit avataan tässä sovelluksessa"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sovelluksessa"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index c58f4b0610f1..7037377156c6 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % à la gauche"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % à la gauche"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Plein écran à la droite"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Inverser l\'appli du haut avec celle du bas"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Inverser l\'appli de gauche avec celle de droite"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Plein écran dans le haut"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % dans le haut"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % dans le haut"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Le menu de l\'appli se trouve ici"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Accéder à l\'affichage sur un ordinateur de bureau pour ouvrir plusieurs applis simultanément"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Revenir au mode Plein écran à tout moment à partir du menu de l\'appli"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette appli"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Agrandir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Réduire <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Fermer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="4419667835599523257">"Poignée de l\'appli"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'appli"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Affichage sur un ordinateur de bureau"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string>
<string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Modifier les proportions"</string>
<string name="close_text" msgid="4986518933445178928">"Fermer"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (affichage sur un ordinateur de bureau)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionner la fenêtre vers la gauche"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionner la fenêtre vers la droite"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Agrandir ou restaurer la taille de la fenêtre"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Agrandir la taille de la fenêtre de l\'appli"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurer la taille de la fenêtre"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Réduire la fenêtre de l\'appli"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fermer la fenêtre de l\'appli"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Ouvrir les paramètres par défaut"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choisissez comment ouvrir les liens Web pour cette appli"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'appli"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index e8db4c929561..fb2ebfd55583 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Écran de gauche à 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Écran de gauche à 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Écran de droite en plein écran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Inverser les applis du haut et du bas"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Inverser les applis de gauche et de droite"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Écran du haut en plein écran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Écran du haut à 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Écran du haut à 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Le menu de l\'application se trouve ici"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Passer à l\'affichage sur ordinateur pour ouvrir plusieurs applications simultanément"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Revenir en plein écran à tout moment depuis le menu de l\'application"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Appuyez deux fois\npour déplacer cette appli"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Agrandir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Réduire <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Fermer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="4419667835599523257">"Poignée de l\'appli"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icône d\'application"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Affichage sur ordinateur"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
<string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flottante"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Modifier le format"</string>
<string name="close_text" msgid="4986518933445178928">"Fermer"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (affichage sur ordinateur)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionner la fenêtre vers la gauche"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionner la fenêtre vers la droite"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Agrandir ou restaurer la taille de la fenêtre"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Agrandir la taille de la fenêtre de l\'application"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurer la taille de la fenêtre"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Réduire la fenêtre de l\'application"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fermer la fenêtre de l\'application"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Ouvrir les paramètres par défaut"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choisir comment ouvrir les liens Web pour cette appli"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'application"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index ba1c4a4b73c6..895cf47e3d53 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % á esquerda"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % á esquerda"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla completa á dereita"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Cambiar a aplicación de arriba pola de abaixo"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Cambiar a aplicación da esquerda pola da dereita"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla completa arriba"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % arriba"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % arriba"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Aquí podes ver o menú da aplicación"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Vai á vista para ordenadores se queres abrir varias aplicacións á vez"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Volve á pantalla completa en calquera momento desde o menú da aplicación"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra outra aplicación para usar a pantalla dividida"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dúas veces para\nmover esta aplicación"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Pechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="4419667835599523257">"Controlador da aplicación"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icona de aplicación"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Vista para ordenadores"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
<string name="more_button_text" msgid="3655388105592893530">"Máis"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar a proporción"</string>
<string name="close_text" msgid="4986518933445178928">"Pechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (vista para ordenadores)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Axustar o tamaño da ventá á esquerda"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Axustar o tamaño da ventá á dereita"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar o tamaño da ventá"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar o tamaño da ventá da aplicación"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar o tamaño da ventá"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar a ventá da aplicación"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Pechar a ventá da aplicación"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Abrir coa configuración predeterminada"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escoller como abrir as ligazóns web para esta aplicación"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na aplicación"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index cd7550893dca..9c8cf96be294 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -102,7 +102,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ઍપ મેનૂ અહીં જોવા મળી શકે છે"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"એકથી વધુ ઍપ એકસાથે ખોલવા માટે ડેસ્કટૉપ વ્યૂ દાખલ કરો"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ઍપ મેનૂમાંથી કોઈપણ સમયે પૂર્ણ સ્ક્રીન પર પાછા ફરો"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"વિભાજિત સ્ક્રીન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string>
@@ -127,7 +128,8 @@
<string name="handle_text" msgid="4419667835599523257">"ઍપનું હૅન્ડલ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ઍપનું આઇકન"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ડેસ્કટૉપ વ્યૂ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string>
<string name="more_button_text" msgid="3655388105592893530">"વધુ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string>
@@ -140,7 +142,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"સાપેક્ષ ગુણોત્તર બદલો"</string>
<string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ડેસ્કટૉપ વ્યૂ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"કદ બદલો"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index d0be7d560961..985dafffa68a 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बाईं स्क्रीन को 50% बनाएं"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"बाईं स्क्रीन को 30% बनाएं"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"दाईं स्क्रीन को फ़ुल स्क्रीन बनाएं"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"सबसे ऊपर मौजूद ऐप्लिकेशन को सबसे नीचे मौजूद ऐप्लिकेशन से बदलें"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"बाईं ओर मौजूद ऐप्लिकेशन को दाईं ओर मौजूद ऐप्लिकेशन से बदलें"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ऊपर की स्क्रीन को फ़ुल स्क्रीन बनाएं"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ऊपर की स्क्रीन को 70% बनाएं"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ऊपर की स्क्रीन को 50% बनाएं"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ऐप्लिकेशन मेन्यू यहां पाया जा सकता है"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"एक साथ कई ऐप्लिकेशन खोलने के लिए, डेस्कटॉप व्यू में जाएं"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ऐप्लिकेशन मेन्यू से फ़ुल स्क्रीन मोड पर किसी भी समय वापस जाएं"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन का इस्तेमाल करने के लिए, किसी अन्य ऐप्लिकेशन को खींचें और छोड़ें"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ऐप्लिकेशन की जगह बदलने के लिए\nदो बार टैप करें"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> की विंडो को बड़ा करें"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> की विंडो को पहले जैसा करें"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> की विंडो को छोटा करें"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> को बंद करें"</string>
<string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
<string name="handle_text" msgid="4419667835599523257">"ऐप्लिकेशन का हैंडल"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ऐप्लिकेशन आइकॉन"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string>
- <string name="desktop_text" msgid="1582173066857454541">"डेस्कटॉप व्यू"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string>
<string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string>
<string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करें"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (डेस्कटॉप व्यू)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"साइज़ बदलें"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"विंडो का साइज़ बाईं ओर से बदलें"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"विंडो का साइज़ दाईं ओर से बढ़ाएं"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"विंडो को बड़ा करें या उसका साइज़ पहले जैसा करें"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ऐप्लिकेशन की विंडो का साइज़ बड़ा करें"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"विंडो का साइज़ पहले जैसा करें"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ऐप्लिकेशन की विंडो को छोटा करें"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ऐप्लिकेशन की विंडो बंद करें"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"डिफ़ॉल्ट सेटिंग के हिसाब से खोलें"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"इस ऐप्लिकेशन के लिए वेब लिंक खोलने का तरीका चुनें"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ऐप्लिकेशन में"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index d3e7599e1dc9..4640bf3b8293 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevi zaslon na 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Lijevi zaslon na 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Desni zaslon u cijeli zaslon"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamijeni gornju aplikaciju donjom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamijeni lijevu aplikaciju desnom"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Gornji zaslon u cijeli zaslon"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gornji zaslon na 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji zaslon na 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Izbornik aplikacije možete pronaći ovdje"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Otvorite prikaz na računalu da biste otvorili više aplikacija zajedno"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Vratite se na cijeli zaslon bilo kad iz izbornika aplikacije"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremjestili ovu aplikaciju"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimiziraj aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Vrati aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimiziraj aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zatvori aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
<string name="handle_text" msgid="4419667835599523257">"Pokazivač aplikacije"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Prikaz na računalu"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string>
<string name="more_button_text" msgid="3655388105592893530">"Više"</string>
<string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promijeni omjer slike"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (prikaz na računalu)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Promijeni veličinu prozora ulijevo"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Promijeni veličinu prozora udesno"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimiziraj ili vrati veličinu prozora"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimiziraj prozor aplikacije"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vrati veličinu prozora"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimiziraj prozor aplikacije"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zatvori prozor aplikacije"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Otvori prema zadanim postavkama"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Odaberite način otvaranja web-veza za ovu aplikaciju"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 2f7a21834eb8..711e2f0bfbbe 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Bal oldali 50%-ra"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Bal oldali 30%-ra"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Jobb oldali teljes képernyőre"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"A felső alkalmazás és az alsó alkalmazás felcserélése"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Bal és jobb alkalmazás felcserélése"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Felső teljes képernyőre"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Felső 70%-ra"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Felső 50%-ra"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Az alkalmazásmenü itt található"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Asztali nézetbe lépve több alkalmazást nyithat meg egyidejűleg"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Az alkalmazásmenüből bármikor visszatérhet a teljes képernyőre"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Koppintson duplán\naz alkalmazás áthelyezéséhez"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> teljes méretűre állítása"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> visszaállítása"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> kis méretűre állítása"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> bezárása"</string>
<string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
<string name="handle_text" msgid="4419667835599523257">"App fogópontja"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Alkalmazásikon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Asztali nézet"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string>
<string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string>
<string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Méretarány módosítása"</string>
<string name="close_text" msgid="4986518933445178928">"Bezárás"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Asztali nézet)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Átméretezés"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ablak átméretezése balra"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ablak átméretezése jobbra"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Ablak teljes méretre állítása vagy visszaállítása"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Alkalmazásablak teljes méretre állítása"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Ablak méretének visszaállítása"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Alkalmazásablak kis méretre állítása"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Alkalmazás ablakának bezárása"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Alapértelmezett beállítások megnyitása"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Az app webes linkjeinek megnyitásához használt módszer"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Az alkalmazásban"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 2898dcc33b35..d7b1b07b6917 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ձախ էկրանը՝ 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ձախ էկրանը՝ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Աջ էկրանը՝ լիաէկրան"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Փոխեք վերևի հավելվածը վերևից"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Փոխեք ձախ հավելվածը աջից"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Վերևի էկրանը՝ լիաէկրան"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Վերևի էկրանը՝ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Վերևի էկրանը՝ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Հավելվածի ընտրացանկն այստեղ է"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Անցեք համակարգչային տարբերակին՝ միաժամանակ մի քանի հավելված բացելու համար"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Ցանկացած ժամանակ հավելվածի ընտրացանկից վերադարձեք լիաէկրան ռեժիմ"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Վերագործարկել"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Այլևս ցույց չտալ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Կրկնակի հպեք՝\nհավելվածը տեղափոխելու համար"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Ծավալել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Վերականգնել «<xliff:g id="APP_NAME">%1$s</xliff:g>» միջոցառումը"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Ծալել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Փակել՝ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
<string name="handle_text" msgid="4419667835599523257">"Հավելվածի կեղծանուն"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Հավելվածի պատկերակ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Համակարգչային տարբերակ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string>
<string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string>
<string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Փոխել կողմերի հարաբերակցությունը"</string>
<string name="close_text" msgid="4986518933445178928">"Փակել"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (համակարգչային տարբերակ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Փոխել չափը"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ձգել պատուհանը դեպի ձախ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ձգել պատուհանը դեպի աջ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Ծավալել կամ վերականգնել պատուհանի չափսը"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Առավելագույնի հասցրեք հավելվածի պատուհանի չափը"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Վերականգնել պատուհանի չափը"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Ծալել հավելվածի պատուհանը"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Փակեք հավելվածի պատուհանը"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Բացել կարգավորումներն ըստ կանխադրման"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Ընտրեք՝ ինչպես բացել այս հավելվածի վեբ հղումները"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Հավելվածում"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 617f9c99de01..1d1927f9dfbc 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kiri 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Layar penuh di kanan"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Tukar aplikasi atas dengan aplikasi bawah"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Tukar aplikasi kiri dengan aplikasi kanan"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Layar penuh di atas"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Atas 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Menu aplikasi dapat ditemukan di sini"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Buka tampilan desktop untuk membuka beberapa aplikasi secara bersamaan"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Kembali ke layar penuh kapan saja dari menu aplikasi"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketuk dua kali untuk\nmemindahkan aplikasi ini"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimalkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Pulihkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimalkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Tutup <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
<string name="handle_text" msgid="4419667835599523257">"Penanganan aplikasi"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikon Aplikasi"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Tampilan Desktop"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string>
<string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string>
<string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ubah rasio aspek"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Tampilan Desktop)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah ukuran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ubah ukuran jendela ke kiri"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ubah ukuran jendela ke kanan"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimalkan atau pulihkan ukuran jendela"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimalkan ukuran jendela aplikasi"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pulihkan ukuran jendela"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalkan jendela aplikasi"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tutup jendela aplikasi"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Buka dengan setelan default"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Pilih cara membuka link web untuk aplikasi ini"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Di aplikasi"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 797a4cc8d035..e94a4c8a08ea 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vinstri 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vinstri 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Hægri á öllum skjánum"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Víxla efsta og neðsta forriti"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Víxla hægra og vinstra forriti"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Efri á öllum skjánum"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Efri 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Efri 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Hér finnurðu forritavalmyndina"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Veldu tölvuútgáfu til að opna mörg forrit samtímis"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Þú getur skipt aftur í allan skjáinn hvenær sem er af forritavalmyndinni"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dragðu annað forrit inn til að nota skjáskiptingu"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ýttu tvisvar til\nað færa þetta forrit"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Stækka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Endurheimta <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minnka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Loka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
<string name="handle_text" msgid="4419667835599523257">"Handfang forrits"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Tákn forrits"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Tölvuútgáfa"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string>
<string name="more_button_text" msgid="3655388105592893530">"Meira"</string>
<string name="float_button_text" msgid="9221657008391364581">"Reikult"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Breyta myndhlutfalli"</string>
<string name="close_text" msgid="4986518933445178928">"Loka"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (tölvuútgáfa)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Breyta stærð"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Breyta stærð glugga til vinstri"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Breyta stærð glugga til hægri"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Hámarka eða endurheimta stærð glugga"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Hámarka stærð forritsglugga"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Endurheimta gluggastærð"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Lágmarka stærð forritsglugga"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Loka forritsglugga"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Stillingar sjálfvirkrar opnunar"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Veldu hvernig veftenglar opnast í forritinu"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Í forritinu"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 85f0f8c2c4d2..5f8334fa011c 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Schermata sinistra al 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Schermata sinistra al 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Schermata destra a schermo intero"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Scambia l\'app in alto con quella in basso"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Scambia l\'app di sinistra con quella di destra"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Schermata superiore a schermo intero"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Schermata superiore al 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Schermata superiore al 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Il menu dell\'app si trova qui"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Entra nella visualizzazione desktop per aprire più app contemporaneamente"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Torna allo schermo intero in qualsiasi momento dal menu dell\'app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trascina in un\'altra app per usare lo schermo diviso"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tocca due volte per\nspostare questa app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Ingrandisci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Ripristina <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Riduci a icona <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Chiudi <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
<string name="handle_text" msgid="4419667835599523257">"Punto di manipolazione app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icona dell\'app"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Visualizzazione desktop"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string>
<string name="more_button_text" msgid="3655388105592893530">"Altro"</string>
<string name="float_button_text" msgid="9221657008391364581">"Mobile"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambia proporzioni"</string>
<string name="close_text" msgid="4986518933445178928">"Chiudi"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (visualizzazione desktop)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ridimensiona"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ridimensiona la finestra a sinistra"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ridimensiona la finestra a destra"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Ingrandisci o ripristina le dimensioni della finestra"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Ingrandisci le dimensioni della finestra dell\'app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Ripristina le dimensioni della finestra"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Riduci a icona la finestra dell\'app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Chiudi finestra dell\'app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Apri in base alle impostazioni predefinite"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Scegli come aprire i link web per questa app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"All\'interno dell\'app"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 36ac62091e43..c3e85230c319 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"שמאלה 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"שמאלה 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"מסך ימני מלא"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"החלפה בין האפליקציה העליונה לבין האפליקציה התחתונה"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"החלפה בין האפליקציה השמאלית לבין האפליקציה הימנית"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"מסך עליון מלא"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"עליון 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"עליון 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר ללחוץ כדי לחזור לגרסה הקודמת"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר ללחוץ כדי לסגור."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"תפריט האפליקציה נמצא כאן"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"כדי לפתוח כמה אפליקציות יחד, צריך לעבור למצב תצוגה למחשב"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"אפשר לחזור למסך מלא בכל שלב מתפריט האפליקציה"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך המפוצל"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"לא להציג שוב"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר ללחוץ לחיצה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"הגדלה של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"שחזור של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"מזעור של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"סגירה של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
<string name="handle_text" msgid="4419667835599523257">"נקודת אחיזה לאפליקציה"</string>
<string name="app_icon_text" msgid="2823268023931811747">"סמל האפליקציה"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string>
- <string name="desktop_text" msgid="1582173066857454541">"תצוגה למחשב"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string>
<string name="more_button_text" msgid="3655388105592893530">"עוד"</string>
<string name="float_button_text" msgid="9221657008391364581">"בלונים"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"שינוי יחס הגובה-רוחב"</string>
<string name="close_text" msgid="4986518933445178928">"סגירה"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> (תצוגה למחשב)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"שינוי הגודל"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"שינוי גודל החלון שמשמאל"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"שינוי גודל החלון שמימין"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"שחזור של גודל החלון או הגדלת החלון"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"הגדלת החלון של האפליקציה"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"שחזור הגודל של החלון"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"מזעור החלון של האפליקציה"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"סגירת החלון של האפליקציה"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"הגדרות לפתיחה כברירת מחדל"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"כאן בוחרים איך לפתוח באפליקציה הזו קישורים לדפי אינטרנט"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"באפליקציה"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 3a12680395c6..1a73dd3b7715 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"左 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"右全画面"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"上下のアプリを入れ替える"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"左右のアプリを入れ替える"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"上部全画面"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"上 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"上 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"アプリメニューはここにあります"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"デスクトップ ビューに切り替えて複数のアプリを同時に開けます"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"アプリメニューからいつでも全画面表示に戻れます"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ダブルタップすると\nこのアプリを移動できます"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を最大化する"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を元に戻す"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を最小化する"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を閉じる"</string>
<string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
<string name="handle_text" msgid="4419667835599523257">"アプリハンドル"</string>
<string name="app_icon_text" msgid="2823268023931811747">"アプリのアイコン"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string>
- <string name="desktop_text" msgid="1582173066857454541">"デスクトップ ビュー"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string>
<string name="more_button_text" msgid="3655388105592893530">"その他"</string>
<string name="float_button_text" msgid="9221657008391364581">"フローティング"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"アスペクト比を変更"</string>
<string name="close_text" msgid="4986518933445178928">"閉じる"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g>(デスクトップ ビュー)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"サイズ変更"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ウィンドウを左側にサイズ変更する"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ウィンドウを右側にサイズ変更する"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ウィンドウを最大化する、またはウィンドウを元のサイズに戻す"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"アプリ ウィンドウを最大化する"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ウィンドウを元のサイズに戻す"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"アプリ ウィンドウを最小化する"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"アプリ ウィンドウを閉じる"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"デフォルトの設定で開く"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"このアプリのウェブリンクを開く方法を選択"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"アプリ内"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 2fbe1a786687..4bbfaefb36a2 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"მარცხენა ეკრანი — 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"მარცხენა ეკრანი — 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"მარჯვენა ნაწილის სრულ ეკრანზე გაშლა"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ზედა და ქვედა აპების მდებარეობის გაცვლა"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"მარცხენა და მარჯვენა აპების მდებარეობის გაცვლა"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ზედა ნაწილის სრულ ეკრანზე გაშლა"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ზედა ეკრანი — 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ზედა ეკრანი — 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"აპის მენიუ შეგიძლიათ იხილოთ აქ"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"რამდენიმე აპის ერთდროულად გასახსნელად შედით დესკტოპის ხედში"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"სრულ ეკრანზე ნებისმიერ დროს შეგიძლიათ დაბრუნდეთ აპის მენიუდან"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ამ აპის გადასატანად\nორმაგად შეეხეთ მას"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის მაქსიმალურად გაშლა"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის აღდგენა"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის ჩაკეცვა"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის დახურვა"</string>
<string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
<string name="handle_text" msgid="4419667835599523257">"აპის იდენტიფიკატორი"</string>
<string name="app_icon_text" msgid="2823268023931811747">"აპის ხატულა"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string>
- <string name="desktop_text" msgid="1582173066857454541">"დესკტოპის ხედი"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string>
<string name="more_button_text" msgid="3655388105592893530">"სხვა"</string>
<string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"თანაფარდობის შეცვლა"</string>
<string name="close_text" msgid="4986518933445178928">"დახურვა"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (დესკტოპის ხედი)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ზომის შეცვლა"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ფანჯრის ზომის შეცვლა მარცხნივ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ფანჯრის ზომის შეცვლა მარჯვნივ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ფანჯრის მაქსიმალურ ზომამდე გაზრდა ან აღდგენა"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"აპის ფანჯრის მაქსიმალურ ზომამდე გაზრდა"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ფანჯრის ზომის აღდგენა"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"აპის ფანჯრის ზომის შემცირება"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"აპის ფანჯრის დახურვა"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"პარამეტრების ნაგულისხმევად გახსნა"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ამ აპისთვის ვებ ბმულების გახსნის წესის არჩევა"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"აპში"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index c494b16687ef..2f6404d4680d 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% сол жақта"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% сол жақта"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Оң жағын толық экранға шығару"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Жоғарыдағы қолданбаны төмендегімен орнын ауыстыру"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Сол жақтағы қолданбаны оң жақтағымен орнын ауыстыру"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Жоғарғы жағын толық экранға шығару"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% жоғарғы жақта"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% жоғарғы жақта"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Қолданба мәзірін осы жерден табуға болады."</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Бірнеше қолданбаны бірге ашу үшін компьютерлік нұсқаны қосыңыз."</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Қолданба мәзірінен кез келген уақытта толық экранға оралыңыз."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлу үшін басқа қолданбаға өтіңіз."</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Өшіріп қосу"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Қайта көрсетілмесін"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бұл қолданбаны басқа орынға\nжылжыту үшін екі рет түртіңіз."</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын ұлғайту"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын қалпына келтіру"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын кішірейту"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын жабу"</string>
<string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
<string name="handle_text" msgid="4419667835599523257">"Қолданба идентификаторы"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Қолданба белгішесі"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Компьютерлік нұсқа"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string>
<string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string>
<string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Арақатынасты өзгерту"</string>
<string name="close_text" msgid="4986518933445178928">"Жабу"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (компьютерлік нұсқа)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлшемін өзгерту"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Терезе өлшемін сол жаққа өзгерту"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Терезе өлшемін оң жаққа өзгерту"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Терезе өлшемін ұлғайту не қалпына келтіру"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Қолданба терезесінің өлшемін ұлғайту"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Терезе өлшемін қалпына келтіру"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Қолданба терезесін кішірейту"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Қолданба терезесін жабу"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Әдепкісінше ашу параметрлері"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Осы қолданбадағы веб-сілтемелерді ашу жолын таңдаңыз"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Қолданбада"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 234c3552b7de..d3942fdc1e6c 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ឆ្វេង 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ឆ្វេង 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"អេក្រង់ពេញខាងស្តាំ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ប្ដូរកម្មវិធីខាងលើគេទៅខាងក្រោម"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ប្ដូរកម្មវិធីខាងឆ្វេងទៅខាងស្ដាំ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"អេក្រង់ពេញខាងលើ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ខាងលើ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ខាងលើ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបាន​ដោះស្រាយ​បញ្ហានេះទេឬ?\nចុចដើម្បី​ត្រឡប់"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមាន​បញ្ហាពាក់ព័ន្ធនឹង​កាមេរ៉ាទេឬ? ចុចដើម្បី​ច្រានចោល។"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"អាចរកឃើញម៉ឺនុយកម្មវិធីនៅទីនេះ"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"ចូលទិដ្ឋភាព​លើកុំព្យូទ័រ ដើម្បីបើកកម្មវិធីច្រើនជាមួយគ្នា"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ត្រឡប់ទៅអេក្រង់ពេញវិញនៅពេលណាក៏បានពីម៉ឺនុយកម្មវិធី"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ចាប់ផ្ដើម​ឡើង​វិញ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"កុំ​បង្ហាញ​ម្ដង​ទៀត"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ចុចពីរដងដើម្បី\nផ្លាស់ទីកម្មវិធីនេះ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"ពង្រីក <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"ស្ដារ <xliff:g id="APP_NAME">%1$s</xliff:g> ឡើងវិញ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"បង្រួម <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"បិទ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
<string name="handle_text" msgid="4419667835599523257">"ឈ្មោះអ្នកប្រើប្រាស់កម្មវិធី"</string>
<string name="app_icon_text" msgid="2823268023931811747">"រូប​កម្មវិធី"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់​ពេញ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ទិដ្ឋភាព​លើកុំព្យូទ័រ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"មុខងារ​បំបែក​អេក្រង់"</string>
<string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string>
<string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ប្ដូរ​​សមាមាត្រ"</string>
<string name="close_text" msgid="4986518933445178928">"បិទ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ទិដ្ឋភាព​លើកុំព្យូទ័រ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ប្ដូរ​ទំហំ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ប្ដូរទំហំវិនដូទៅឆ្វេង"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ប្ដូរទំហំវិនដូទៅស្ដាំ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ស្ដារ ឬបង្កើនទំហំវិនដូជាអតិបរមា"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ពង្រីកទំហំវិនដូកម្មវិធី"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ស្ដារទំហំវិនដូ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"បង្រួមវិនដូកម្មវិធី"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"បិទវិនដូកម្មវិធី"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ការកំណត់បើកតាមលំនាំដើម"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ជ្រើសរើសរបៀបបើកតំណបណ្ដាញសម្រាប់កម្មវិធីនេះ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"នៅក្នុងកម្មវិធី"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 61ee3c3915db..fb15e34d934b 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% ಎಡಕ್ಕೆ"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% ಎಡಕ್ಕೆ"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ಬಲ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ಮೇಲಿನ ಮತ್ತು ಕೆಳಗಿನ ಆ್ಯಪ್‌ಗಳನ್ನು ಅದಲು-ಬದಲು ಮಾಡಿ"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ಎಡ ಮತ್ತು ಬಲದ ಆ್ಯಪ್‌ಗಳನ್ನು ಅದಲು-ಬದಲು ಮಾಡಿ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ಮೇಲಿನ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% ಮೇಲಕ್ಕೆ"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% ಮೇಲಕ್ಕೆ"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ಆ್ಯಪ್ ಮೆನುವನ್ನು ಇಲ್ಲಿ ಕಾಣಬಹುದು"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"ಹಲವು ಆ್ಯಪ್‌ಗಳನ್ನು ಒಟ್ಟಿಗೆ ತೆರೆಯಲು ಡೆಸ್ಕ್‌ಟಾಪ್ ವೀಕ್ಷಣೆಯನ್ನು ನಮೂದಿಸಿ"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ಆ್ಯಪ್ ಮೆನುವಿನಿಂದ ಯಾವಾಗ ಬೇಕಾದರೂ ಫುಲ್‌ಸ್ಕ್ರೀನ್‌ಗೆ ಹಿಂತಿರುಗಿ"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಸರಿಸಲು\nಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮರುಸ್ಥಾಪಿಸಿ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿ"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮುಚ್ಚಿ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
<string name="handle_text" msgid="4419667835599523257">"ಆ್ಯಪ್ ಹ್ಯಾಂಡಲ್"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್‌ಸ್ಕ್ರೀನ್"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ಡೆಸ್ಕ್‌ಟಾಪ್ ವೀಕ್ಷಣೆ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
<string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string>
<string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ಡೆಸ್ಕ್‌ಟಾಪ್ ವೀಕ್ಷಣೆ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ಮರುಗಾತ್ರಗೊಳಿಸಿ ವಿಂಡೋವನ್ನು ಎಡಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ಮರುಗಾತ್ರಗೊಳಿಸಿ ವಿಂಡೋವನ್ನು ಬಲಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ವಿಂಡೋ ಗಾತ್ರವನ್ನು ಗರಿಷ್ಠಗೊಳಿಸಿ ಅಥವಾ ಮರುಸ್ಥಾಪಿಸಿ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ಆ್ಯಪ್ ವಿಂಡೋದ ಗಾತ್ರವನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ವಿಂಡೋ ಗಾತ್ರವನ್ನು ಮರುಸ್ಥಾಪಿಸಿ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ಮುಚ್ಚಿರಿ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ಡೀಫಾಲ್ಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಂದ ತೆರೆಯಿರಿ"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ಈ ಆ್ಯಪ್‌ಗೆ ವೆಬ್ ಲಿಂಕ್‌ಗಳನ್ನು ಹೇಗೆ ತೆರೆಯಬೇಕು ಎಂಬುದನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ಆ್ಯಪ್‌ನಲ್ಲಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 2b36ba1eb3d7..dbfd32a7dcb1 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"왼쪽 화면 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"왼쪽 화면 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"오른쪽 화면 전체화면"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"상단 앱과 하단 앱 바꾸기"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"왼쪽 앱과 오른쪽 앱 바꾸기"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"위쪽 화면 전체화면"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"위쪽 화면 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"위쪽 화면 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"앱 메뉴는 여기에서 찾을 수 있습니다."</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"데스크톱 뷰를 실행하여 여러 앱을 함께 열 수 있습니다."</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"언제든지 앱 메뉴에서 전체 화면으로 돌아갈 수 있습니다."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"다시 시작"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"다시 표시 안함"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"두 번 탭하여\n이 앱 이동"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> 최대화"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> 복원"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> 최소화"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> 닫기"</string>
<string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
<string name="handle_text" msgid="4419667835599523257">"앱 핸들"</string>
<string name="app_icon_text" msgid="2823268023931811747">"앱 아이콘"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string>
- <string name="desktop_text" msgid="1582173066857454541">"데스크톱 뷰"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string>
<string name="more_button_text" msgid="3655388105592893530">"더보기"</string>
<string name="float_button_text" msgid="9221657008391364581">"플로팅"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"가로세로 비율 변경"</string>
<string name="close_text" msgid="4986518933445178928">"닫기"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g>(데스크톱 뷰)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"크기 조절"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"창 크기 왼쪽으로 조절"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"창 크기 오른쪽으로 조절"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"창 최대화 또는 크기 복원"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"앱 창 크기 최대화"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"창 크기 복원"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"앱 창 최소화"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"앱 창 닫기"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"기본값으로 열기 설정"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"이 앱에서 웹 링크를 여는 방법을 선택하세요"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"앱에서"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index bb3c2fdae471..36194cdaaa47 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Сол жактагы экранды 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Сол жактагы экранды 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Оң жактагы экранды толук экран режимине өткөрүү"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Өйдө жактагы колдонмону ылдый жактагы менен алмаштыруу"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Сол жактагы колдонмону оң жактагы менен алмаштыруу"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Үстүнкү экранды толук экран режимине өткөрүү"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Үстүнкү экранды 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Үстүнкү экранды 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Колдонмонун менюсун ушул жерден таба аласыз"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Бир убакта бир нече колдонмону ачуу үчүн компьютердик версияга өтүңүз"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Каалаган убакта колдонмонун менюсунан толук экранга кайта аласыз"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бул колдонмону жылдыруу үчүн\nэки жолу таптаңыз"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> чоңойтуу"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> калыбына келтирүү"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> кичирейтүү"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> жабуу"</string>
<string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
<string name="handle_text" msgid="4419667835599523257">"Колдонмонун маркери"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Колдонмонун сүрөтчөсү"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Компьютердик версия"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string>
<string name="more_button_text" msgid="3655388105592893530">"Дагы"</string>
<string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Тараптардын катнашын өзгөртүү"</string>
<string name="close_text" msgid="4986518933445178928">"Жабуу"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (компьютердик версия)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлчөмүн өзгөртүү"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Терезенин өлчөмүн солго өзгөртүү"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Терезенин өлчөмүн оңго өзгөртүү"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Терезенин өлчөмүн чоңойтуу же калыбына келтирүү"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Колдонмонун терезесинин өлчөмүн чоңойтуу"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Колдонмонун терезесинин өлчөмүн калыбына келтирүү"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Колдонмонун терезесин кичирейтүү"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Колдонмонун терезесин жабуу"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Демейки шартта ачылуучу шилтемелердин параметрлери"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Колдонмодо шилтемелер кантип ачыларын тандаңыз"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Колдонмодо"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 8850923d8cca..671f92447204 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ຊ້າຍ 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ຊ້າຍ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ເຕັມໜ້າຈໍຂວາ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ສະຫຼັບແອັບທາງເທິງກັບທາງລຸ່ມ"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ສະຫຼັບແອັບທາງຊ້າຍກັບທາງຂວາ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ເຕັມໜ້າຈໍເທິງສຸດ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ເທິງສຸດ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ເທິງສຸດ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອ​ປິດ​ໄວ້."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ສາມາດເບິ່ງເມນູແອັບໄດ້ບ່ອນນີ້"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"ເຂົ້າສູ່ມຸມມອງເດັສທັອບເພື່ອເປີດຫຼາຍແອັບພ້ອມກັນ"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ກັບຄືນໄປຫາໂໝດເຕັມຈໍໄດ້ທຸກເວລາຈາກເມນູແອັບ"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ລາກໄປໄວ້ໃນແອັບອື່ນເພື່ອແບ່ງໜ້າຈໍ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ແຕະສອງເທື່ອເພື່ອ\nຍ້າຍແອັບນີ້"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"ຂະຫຍາຍ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"ກູ້ຄືນ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"ຫຍໍ້ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"ປິດ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
<string name="handle_text" msgid="4419667835599523257">"ຊື່ຜູ້ໃຊ້ແອັບ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ໄອຄອນແອັບ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ເຕັມຈໍ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ມຸມມອງຢູ່ເດັສທັອບ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"ແບ່ງໜ້າຈໍ"</string>
<string name="more_button_text" msgid="3655388105592893530">"ເພີ່ມເຕີມ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ລອຍ"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ປ່ຽນອັດຕາສ່ວນຮູບ"</string>
<string name="close_text" msgid="4986518933445178928">"ປິດ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ມຸມມອງຢູ່ເດັສທັອບ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ປັບຂະໜາດ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ປັບຂະໜາດໜ້າຈໍໄປທາງຊ້າຍ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ປັບຂະໜາດໜ້າຈໍໄປທາງຂວາ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ຂະຫຍາຍ ຫຼື ຄືນຄ່າຂະໜາດໜ້າຈໍ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ຂະຫຍາຍຂະໜາດໜ້າຈໍແອັບໃຫ້ໃຫຍ່ສຸດ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ກູ້ຄືນຂະໜາດໜ້າຈໍ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ຫຍໍ້ໜ້າຈໍແອັບ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ປິດໜ້າຈໍແອັບ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ເປີດຕາມການຕັ້ງຄ່າເລີ່ມຕົ້ນ"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ເລືອກວິທີເປີດລິ້ງເວັບສຳລັບແອັບນີ້"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ໃນແອັບ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 8ed826aa11fc..794c5ab02c19 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kairysis ekranas 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kairysis ekranas 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Dešinysis ekranas viso ekrano režimu"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Sukeisti viršutiniąją programą su apatiniąja"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Sukeisti kairiąją programą su dešiniąja"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Viršutinis ekranas viso ekrano režimu"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Viršutinis ekranas 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Viršutinis ekranas 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Programos meniu rasite čia"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Įjunkite rodinio versiją staliniams kompiuteriams, kad vienu metu atidarytumėte kelias programas"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Bet kada iš programos meniu grįžkite į viso ekrano režimą"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dukart palieskite, kad\nperkeltumėte šią programą"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Padidinti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Atkurti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Sumažinti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Uždaryti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
<string name="handle_text" msgid="4419667835599523257">"Programos kreipinys"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Programos piktograma"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Rodinio versija staliniams kompiuteriams"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string>
<string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string>
<string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Keisti kraštinių santykį"</string>
<string name="close_text" msgid="4986518933445178928">"Uždaryti"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ (rodinio versija staliniams kompiuteriams)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Pakeisti dydį"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Pakeisti lango dydį kairėje"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Pakeisti lango dydį dešinėje"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Padidinti arba atkurti lango dydį"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Padidinti programos lango dydį"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Atkurti lango dydį"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Sumažinti programos langą"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Uždaryti programos langą"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Atidaryti pagal numatytuosius nustatymus"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Pasirinkite, kaip atidaryti šios programos žiniatinklio nuorodas"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Programoje"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 1227055b3222..5b44112c76d5 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pa kreisi 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Pa kreisi 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Labā daļa pa visu ekrānu"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Apmainīt vietām lietotni augšpusē ar lietotni apakšpusē"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Apmainīt vietām lietotni kreisajā pusē ar lietotni labajā pusē"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Augšdaļa pa visu ekrānu"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Augšdaļa 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Augšdaļa 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Šeit ir pieejama lietotņu izvēlne"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Lai atvērtu vairākas lietotnes vienlaikus, pārejiet uz skatu datorā"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"No lietotnes izvēlnes varat jebkurā brīdī atgriezties pilnekrāna režīmā."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Veiciet dubultskārienu,\nlai pārvietotu šo lietotni"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimizēt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Atjaunot lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizēt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Aizvērt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
<string name="handle_text" msgid="4419667835599523257">"Lietotnes turis"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Lietotnes ikona"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Skats datorā"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string>
<string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string>
<string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mainīt malu attiecību"</string>
<string name="close_text" msgid="4986518933445178928">"Aizvērt"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (skats datorā)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Mainīt lielumu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Mainīt loga lielumu uz kreiso pusi"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Mainīt loga lielumu uz labo pusi"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimizēt vai atjaunot loga lielumu"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimizēt lietotnes loga lielumu"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Atjaunot loga lielumu"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizēt lietotnes logu"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Aizvērt lietotnes logu"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Atvērt pēc noklusējuma iestatījumiem"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Izvēlieties, kā atvērt šajā lietotnē norādītās saites"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Lietotnē"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index eb513748cdba..96bf9b67144e 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левиот 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Левиот 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Десниот на цел екран"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Заменете ги местата на горната и долната апликација"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Заменете ги местата на левата и десната апликација"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Горниот на цел екран"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Горниот 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горниот 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Менито со апликации може да го најдете овде"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Влезете во приказот на компјутер за да отворите повеќе апликации заедно"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Вратете се на цел екран од менито со апликации кога сакате"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Повлечете друга апликација за поделен екран"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Допрете двапати за да ја\nпоместите апликацијава"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Максимизирај <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Врати <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Минимизирај <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Затвори <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Прекар на апликацијата"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Икона на апликацијата"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Цел екран"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Приказ на компјутер"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Поделен екран"</string>
<string name="more_button_text" msgid="3655388105592893530">"Повеќе"</string>
<string name="float_button_text" msgid="9221657008391364581">"Лебдечко"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промени го соодносот"</string>
<string name="close_text" msgid="4986518933445178928">"Затворете"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (приказ на компјутер)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Смени големина"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Променете ја големината на прозорецот налево"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Променете ја големината на прозорецот надесно"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Максимизирајте или вратете ја големината на прозорецот"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Максимизирај ја големината на прозорецот на апликацијата"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Врати ја големината на прозорецот"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Минимизирај го прозорецот на апликацијата"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Затвори го прозорецот на апликацијата"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Отвори според стандардните поставки"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Изберете како да се отвораат линковите за апликацијава"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Во апликацијата"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index c48cbce331c4..8085601076ef 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ഇടത് 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ഇടത് 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"വലത് പൂർണ്ണ സ്ക്രീൻ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"മുകളിലെയും താഴത്തെയും ആപ്പ് സ്വാപ്പ് ചെയ്യുക"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ഇടതുവശത്തെയും വലതുവശത്തെയും ആപ്പ് സ്വാപ്പ് ചെയ്യുക"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"മുകളിൽ പൂർണ്ണ സ്ക്രീൻ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"മുകളിൽ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"മുകളിൽ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ആപ്പ് മെനു ഇവിടെ കണ്ടെത്താനാകും"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"ഒന്നിലധികം ആപ്പുകൾ ഒരുമിച്ച് തുറക്കാൻ ഡെസ്‌ക്‌‌ടോപ്പ് വ്യൂവിൽ പ്രവേശിക്കുക"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ആപ്പ് മെനുവിൽ നിന്ന് ഏതുസമയത്തും പൂർണ്ണ സ്‌ക്രീനിലേക്ക് മടങ്ങുക"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ഈ ആപ്പ് നീക്കാൻ\nഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> പരമാവധിയാക്കുക"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> പുനഃസ്ഥാപിക്കുക"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ചുരുക്കുക"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> അടയ്ക്കുക"</string>
<string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
<string name="handle_text" msgid="4419667835599523257">"ആപ്പ് ഹാൻഡിൽ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ആപ്പ് ഐക്കൺ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ഡെസ്‌ക്ടോപ്പ് വ്യൂ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"സ്‌ക്രീൻ വിഭജനം"</string>
<string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"വീക്ഷണ അനുപാതം മാറ്റുക"</string>
<string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ഡെസ്‌ക്ടോപ്പ് വ്യൂ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്‌ക്രീൻ വലുതാക്കുക"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"വലുപ്പം മാറ്റുക"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ഇടത്തേക്ക് ആപ്പ് വിൻഡോ വലുപ്പം മാറ്റുക"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"വലത്തേക്ക് ആപ്പ് വിൻഡോ വലുപ്പം മാറ്റുക"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"വിന്‍ഡോ വലുപ്പം വലുതാക്കുക അല്ലെങ്കിൽ പഴയത് പുനഃസ്ഥാപിക്കുക"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ആപ്പ് വിൻഡോ വലുപ്പം പരമാവധിയാക്കുക"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"വിൻഡോ വലുപ്പം പുനഃസ്ഥാപിക്കുക"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ആപ്പ് വിൻഡോ ചുരുക്കുക"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ആപ്പ് വിൻഡോ അടയ്ക്കുക"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ഡിഫോൾട്ട് ക്രമീകരണം ഉപയോഗിച്ച് തുറക്കുക"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ഈ ആപ്പിനായി വെബ് ലിങ്കുകൾ എങ്ങനെ തുറക്കണമെന്ന് തിരഞ്ഞെടുക്കൂ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ആപ്പിൽ"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index fca2f7ce9177..5efe62d7d837 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Зүүн 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Зүүн 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Баруун талын бүтэн дэлгэц"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Дээд талын аппыг доод талынхаар солих"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Зүүн талын аппыг баруун талынхаар солих"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Дээд талын бүтэн дэлгэц"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Дээд 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Дээд 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Аппын цэсийг эндээс олох боломжтой"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Олон аппыг хамтад нь нээхийн тулд дэлгэц дээр харагдах байдалд орно уу"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Аппын цэсээс бүтэн дэлгэц рүү хүссэн үедээ буцна уу"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Дэлгэц хуваах горимд ашиглахын тулд өөр аппыг чирнэ үү"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Дахин эхлүүлэх"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Дахиж бүү харуул"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Энэ аппыг зөөхийн тулд\nхоёр товшино уу"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г томруулах"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г сэргээх"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г жижгэрүүлэх"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г хаах"</string>
<string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
<string name="handle_text" msgid="4419667835599523257">"Аппын бариул"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Aппын дүрс тэмдэг"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Дэлгэц дээр харагдах байдал"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string>
<string name="more_button_text" msgid="3655388105592893530">"Бусад"</string>
<string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Аспектын харьцааг өөрчлөх"</string>
<string name="close_text" msgid="4986518933445178928">"Хаах"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (дэлгэц дээр харагдах байдал)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Хэмжээг өөрчлөх"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Цонхны хэмжээг зүүн тал руу өөрчлөх"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Цонхны хэмжээг баруун тал руу өөрчлөх"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Цонхны хэмжээг томруулах эсвэл сэргээх"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Аппын цонхны хэмжээг томруулах"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Цонхны хэмжээг сэргээх"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Аппын цонхыг жижгэрүүлэх"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Аппын цонхыг хаах"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Өгөгдмөл тохиргоогоор нээх"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Энэ аппад веб холбоосыг хэрхэн нээхийг сонгоно уу"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Аппад"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 888118732b1a..e10c8d82440d 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"डावी 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"डावी 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"उजवी फुल स्क्रीन"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"सर्वात वरचे अ‍ॅप हे तळाशी असलेल्या अ‍ॅपने स्वॅप करा"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"डावीकडील अ‍ॅप हे उजवीकडील अ‍ॅपने स्वॅप करा"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"शीर्ष फुल स्क्रीन"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"शीर्ष 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"शीर्ष 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्‍यासाठी टॅप करा."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ॲप मेनू इथे आढळू शकतो"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"एकाहून अधिक ॲप्स एकत्र उघडण्यासाठी डेस्कटॉप दृश्यात एंटर करा"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ॲप मेनूमधून कधीही फुल स्क्रीनवर परत या"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप ड्रॅग करा"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"हे ॲप हलवण्यासाठी\nदोनदा टॅप करा"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> मोठे करा"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> रिस्टोअर करा"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> लहान करा"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> बंद करा"</string>
<string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
<string name="handle_text" msgid="4419667835599523257">"अ‍ॅपचे हँडल"</string>
<string name="app_icon_text" msgid="2823268023931811747">"अ‍ॅप आयकन"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"फुलस्‍क्रीन"</string>
- <string name="desktop_text" msgid="1582173066857454541">"डेस्कटॉप दृश्य"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string>
<string name="more_button_text" msgid="3655388105592893530">"आणखी"</string>
<string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"आस्पेक्ट रेशो बदला"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करा"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (डेस्कटॉप दृश्य)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदला"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अ‍ॅप इथे हलवू शकत नाही"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"अ‍ॅप विंडोचा डावीकडे आकार बदला"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"अ‍ॅप विंडोचा उजवीकडे आकार बदला"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"विंडोचा आकार मोठा करा किंवा रिस्टोअर करा"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"अ‍ॅप विंडोचा आकार मोठा करा"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"विंडोचा आकार रिस्टोअर करा"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"अ‍ॅप विंडो लहान करा"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"अ‍ॅप विंडो बंद करा"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"बाय डीफॉल्ट सेटिंग्ज उघडा"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"या अ‍ॅपसाठीच्या वेब लिंक कशा उघडाव्यात हे निवडा"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ॲपमध्ये"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index f0cd560b1f48..e18e7ec68640 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kiri 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Skrin penuh kanan"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Tukar apl bahagian atas dengan apl bahagian bawah"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Tukar apl sebelah kiri dengan apl sebelah kanan"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Skrin penuh atas"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Atas 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Menu apl boleh ditemukan di sini"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Masuki paparan desktop untuk membuka berbilang apl serentak"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Kembali kepada skrin penuh pada bila-bila masa daripada menu apl"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Seret masuk apl lain untuk menggunakan skrin pisah"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketik dua kali untuk\nalih apl ini"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimumkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Pulihkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimumkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Tutup <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
<string name="handle_text" msgid="4419667835599523257">"Pengendalian apl"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikon Apl"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Skrin penuh"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Paparan Desktop"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Skrin Pisah"</string>
<string name="more_button_text" msgid="3655388105592893530">"Lagi"</string>
<string name="float_button_text" msgid="9221657008391364581">"Terapung"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tukar nisbah bidang"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Paparan Desktop)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah saiz"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ubah saiz tetingkap ke sebelah kiri"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ubah saiz tetingkap ke sebelah kanan"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimumkan atau pulihkan saiz tetingkap"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimumkan saiz tetingkap apl"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pulihkan saiz tetingkap"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimumkan tetingkap apl"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tutup tetingkap apl"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Buka tetapan secara lalai"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Pilih cara membuka pautan web untuk apl ini"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Pada apl"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 3451701a0456..334a79799d53 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -43,10 +43,8 @@
<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>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"အပေါ်အက်ပ်ကို အောက်သို့ ပြောင်းရန်"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ဘယ်ဘက်အက်ပ်ကို ညာဘက်သို့ ပြောင်းရန်"</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>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"အက်ပ်မီနူးကို ဤနေရာတွင် တွေ့နိုင်သည်"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"အက်ပ်များစွာကို အတူတကွဖွင့်ရန်အတွက် ဒက်စ်တော့မြင်ကွင်းသို့ ဝင်ရောက်နိုင်သည်"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"အက်ပ်မီနူးမှ ဖန်သားပြင်အပြည့်သို့ အချိန်မရွေး ပြန်သွားနိုင်သည်"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းအတွက် အက်ပ်နောက်တစ်ခုကို ဖိဆွဲပါ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ပြန်စရန်"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"နောက်ထပ်မပြပါနှင့်"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ဤအက်ပ်ကို ရွှေ့ရန်\nနှစ်ချက်တို့ပါ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ချဲ့ရန်"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ပြန်ယူရန်"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ချုံ့ရန်"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ပိတ်ရန်"</string>
<string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
<string name="handle_text" msgid="4419667835599523257">"အက်ပ်သုံးသူအမည်"</string>
<string name="app_icon_text" msgid="2823268023931811747">"အက်ပ်သင်္ကေတ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ဒက်စ်တော့ မြင်ကွင်း"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string>
<string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string>
<string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"အချိုးအစား ပြောင်းရန်"</string>
<string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ဒက်စ်တော့ မြင်ကွင်း)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"အရွယ်ပြင်ရန်"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ဝင်းဒိုးကို ဘယ်ဘက်သို့ အရွယ်ပြင်ရန်"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ဝင်းဒိုးကို ညာဘက်သို့ အရွယ်ပြင်ရန်"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ဝင်းဒိုးအရွယ်အစားကို ချဲ့ရန် (သို့) ပြန်ပြောင်းရန်"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"အက်ပ်ဝင်းဒိုး အရွယ်အစားကို ချဲ့ရန်"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ဝင်းဒိုးအရွယ်အစား ပြန်ပြောင်းရန်"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"အက်ပ်ဝင်းဒိုးကို ချုံ့ရန်"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"အက်ပ်ဝင်းဒိုးကို ပိတ်ရန်"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"မူရင်းဆက်တင်ဖြင့် ဖွင့်ရန်"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ဤအက်ပ်အတွက် ဝဘ်လင့်ခ်များ မည်သို့ဖွင့်မည်ကို ရွေးပါ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"အက်ပ်တွင်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 3a4100c5f154..7e21b475b2fa 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sett størrelsen på den venstre delen av skjermen til 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Sett størrelsen på den venstre delen av skjermen til 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Utvid den høyre delen av skjermen til hele skjermen"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Bytt om på den øvre og nedre appen"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Bytt om på venstre og høyre app"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Utvid den øverste delen av skjermen til hele skjermen"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Sett størrelsen på den øverste delen av skjermen til 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Sett størrelsen på den øverste delen av skjermen til 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Her finner du appmenyen"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Gå til skrivebordsvisningen for å åpne flere apper samtidig"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Du kan gå tilbake til fullskjermmodusen når som helst fra appmenyen"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra inn en annen app for å bruke delt skjerm"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dobbelttrykk for\nå flytte denne appen"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Gjenopprett <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Lukk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
<string name="handle_text" msgid="4419667835599523257">"Apphåndtak"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Datamaskinvisning"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
<string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Endre høyde/bredde-forholdet"</string>
<string name="close_text" msgid="4986518933445178928">"Lukk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (datamaskinvisning)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Endre størrelse"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Endre størrelsen på vinduet til venstre"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Endre størrelsen på vinduet til høyre"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimer eller gjenopprett størrelsen på vinduet"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimer størrelsen på appvinduet"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Gjenopprett vindusstørrelsen"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimer appvinduet"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Lukk appvinduet"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Innstillinger for åpning som standard"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Velg hvordan nettlinker skal åpnes for denne appen"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index cc31e384260c..976f8378606a 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -43,10 +43,8 @@
<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>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"सिरान र पुछारको एप अदलबदल गर्नुहोस्"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"दायाँ र बायाँतिरको एप अदलबदल गर्नुहोस्"</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>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"एपको मेनु यहाँ भेट्टाउन सकिन्छ"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"एकभन्दा बढी एपहरू सँगै देखाउन डेस्कटप भ्यू हाल्नुहोस्"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"जुनसुकै बेला एपको मेनुबाट फुल स्क्रिनमा फर्कनुहोस्"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाउनुहोस्"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"यो एप सार्न डबल\nट्याप गर्नुहोस्"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> म्याक्सिमाइज गर्नुहोस्"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> रिस्टोर गर्नुहोस्"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> मिनिमाइज गर्नुहोस्"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> बन्द गर्नुहोस्"</string>
<string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
<string name="handle_text" msgid="4419667835599523257">"एपको ह्यान्डल"</string>
<string name="app_icon_text" msgid="2823268023931811747">"एपको आइकन"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string>
- <string name="desktop_text" msgid="1582173066857454541">"डेस्कटप भ्यू"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string>
<string name="more_button_text" msgid="3655388105592893530">"थप"</string>
<string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string>
<string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (डेस्कटप भ्यू)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदल्नुहोस्"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"विन्डोको आकार बदलेर बायाँतिर लैजानुहोस्"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"विन्डोको आकार बदलेर दायाँतिर लैजानुहोस्"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"विन्डोको आकार म्याक्सिमाइज गर्नुहोस् वा रिस्टोर गर्नुहोस्"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"एपको विन्डोको आकार म्याक्सिमाइज गर्नुहोस्"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"विन्डोको आकार रिस्टोर गर्नुहोस्"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"एपको विन्डो मिनिमाइज गर्नुहोस्"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"एपको विन्डो बन्द गर्नुहोस्"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"डिफल्ट सेटिङअनुसार खोल्नुहोस्"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"यो एपका वेब लिंकहरू खोल्ने तरिका छनौट गर्नुहोस्"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"एपमा"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 4234ddf0e456..7178e418a5aa 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Linkerscherm 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Linkerscherm 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Rechterscherm op volledig scherm"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Apps bovenaan en onderaan omwisselen"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Apps links en rechts omwisselen"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Bovenste scherm op volledig scherm"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Bovenste scherm 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bovenste scherm 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Het app-menu vind je hier"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Ga naar de desktopweergave om meerdere apps tegelijk te openen"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Ga wanneer je wilt terug naar volledig scherm vanuit het app-menu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep een andere app hier naartoe om het scherm te splitsen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\ndeze app te verplaatsen"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> maximaliseren"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> herstellen"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> minimaliseren"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> sluiten"</string>
<string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
<string name="handle_text" msgid="4419667835599523257">"App-handgreep"</string>
<string name="app_icon_text" msgid="2823268023931811747">"App-icoon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktopweergave"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string>
<string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
<string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Beeldverhouding wijzigen"</string>
<string name="close_text" msgid="4986518933445178928">"Sluiten"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (desktopweergave)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Formaat aanpassen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Formaat van venster naar links aanpassen"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Formaat van venster naar rechts aanpassen"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Formaat van venster maximaliseren of herstellen"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Formaat van app-venster maximaliseren"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vensterformaat herstellen"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"App-venster minimaliseren"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"App-venster sluiten"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Instellingen voor Standaard openen"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Kies hoe je weblinks voor deze app wilt openen"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In de app"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 6390d37a3f57..19cc8ced6517 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ବାମ ପଟକୁ 50% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ବାମ ପଟେ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ଡାହାଣ ପଟକୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍ କରନ୍ତୁ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ବଟମ ସହ ଟପକୁ ଆପ ସ୍ବାପ କରନ୍ତୁ"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ଡାହାଣ ସହ ବାମକୁ ଆପ ସ୍ବାପ କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ଉପର ଆଡ଼କୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍ କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ଉପର ଆଡ଼କୁ 70% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ଉପର ଆଡ଼କୁ 50% କରନ୍ତୁ"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ଆପ ମେନୁ ଏଠାରେ ମିଳିପାରିବ"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"ଏକାଠି ଏକାଧିକ ଆପ୍ସ ଖୋଲିବାକୁ ଡେସ୍କଟପ ଭ୍ୟୁରେ ପ୍ରବେଶ କରନ୍ତୁ"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ଆପ ମେନୁରୁ ଯେ କୌଣସି ସମୟରେ ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନକୁ ଫେରନ୍ତୁ"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ଏହି ଆପକୁ ମୁଭ\nକରିବା ପାଇଁ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଛୋଟ କରନ୍ତୁ"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
<string name="handle_text" msgid="4419667835599523257">"ଆପର ହେଣ୍ଡେଲ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ଆପ ଆଇକନ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ଡେସ୍କଟପ ଭ୍ୟୁ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
<string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ଡେସ୍କଟପ ଭ୍ୟୁ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ରିସାଇଜ କରନ୍ତୁ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ବାମପଟକୁ ୱିଣ୍ଡୋ ରିସାଇଜ କରନ୍ତୁ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ଡାହାଣପଟକୁ ୱିଣ୍ଡୋ ରିସାଇଜ କରନ୍ତୁ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ୱିଣ୍ଡୋ ସାଇଜକୁ ମେକ୍ସିମାଇଜ କିମ୍ବା ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ଆପ ଇଣ୍ଡୋ ଆକାର ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ୱିଣ୍ଡୋ ଆକାର ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ଆପ ୱିଣ୍ଡୋକୁ ମିନିମାଇଜ କରନ୍ତୁ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ଆପ ୱିଣ୍ଡୋ ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ଡିଫଲ୍ଟ ସେଟିଂସକୁ ଖୋଲନ୍ତୁ"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ଏହି ଆପ ପାଇଁ ୱେବ ଲିଙ୍କଗୁଡ଼ିକୁ କିପରି ଖୋଲିବେ, ତାହା ବାଛନ୍ତୁ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ଆପରେ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index d2b837c5df3a..7e8929003017 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ਖੱਬੇ 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ਖੱਬੇ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ਸੱਜੇ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ਸਿਖਰਲੀ ਐਪ ਨੂੰ ਹੇਠਲੀ ਨਾਲ ਬਦਲੋ"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ਖੱਬੀ ਐਪ ਨੂੰ ਸੱਜੀ ਨਾਲ ਬਦਲੋ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ਉੱਪਰ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ਉੱਪਰ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ਉੱਪਰ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ਐਪ ਮੀਨੂ ਇੱਥੇ ਮਿਲ ਸਕਦਾ ਹੈ"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"ਕਈ ਐਪਾਂ ਨੂੰ ਇਕੱਠੇ ਖੋਲ੍ਹਣ ਲਈ ਡੈਸਕਟਾਪ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ਐਪ ਮੀਨੂ ਤੋਂ ਕਿਸੇ ਵੀ ਸਮੇਂ ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਓ"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ਇਸ ਐਪ ਦਾ ਟਿਕਾਣਾ ਬਦਲਣ ਲਈ\nਡਬਲ ਟੈਪ ਕਰੋ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਵੱਡਾ ਕਰੋ"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਛੋਟਾ ਕਰੋ"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਬੰਦ ਕਰੋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
<string name="handle_text" msgid="4419667835599523257">"ਐਪ ਹੈਂਡਲ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ਐਪ ਪ੍ਰਤੀਕ"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ਡੈਸਕਟਾਪ ਦ੍ਰਿਸ਼"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
<string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string>
<string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string>
<string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ਡੈਸਕਟਾਪ ਦ੍ਰਿਸ਼)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ਆਕਾਰ ਬਦਲੋ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਬਦਲ ਕੇ ਖੱਬੇ ਪਾਸੇ ਕਰੋ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਬਦਲ ਕੇ ਸੱਜੇ ਪਾਸੇ ਕਰੋ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਵਧਾਓ ਜਾਂ ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ਐਪ ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ਵਿੰਡੋ ਦੇ ਆਕਾਰ ਨੂੰ ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ਐਪ ਵਿੰਡੋ ਨੂੰ ਛੋਟਾ ਕਰੋ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ਐਪ ਵਿੰਡੋ ਬੰਦ ਕਰੋ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੈਟਿੰਗਾਂ ਮੁਤਾਬਕ ਖੋਲ੍ਹੋ"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ਇਸ ਐਪ ਲਈ ਵੈੱਬ ਲਿੰਕਾਂ ਨੂੰ ਖੋਲ੍ਹਣ ਦਾ ਤਰੀਕਾ ਚੁਣੋ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ਐਪ ਵਿੱਚ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 59deb01e689f..58a691f83cca 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% lewej części ekranu"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% lewej części ekranu"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Prawa część ekranu na pełnym ekranie"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamień aplikację na górze z tą na dole"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamień aplikację po lewej z tą po prawej"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Górna część ekranu na pełnym ekranie"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% górnej części ekranu"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% górnej części ekranu"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Tu znajdziesz menu aplikacji"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Aby otworzyć kilka aplikacji jednocześnie, przejdź do widoku pulpitu"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Z menu aplikacji w każdej chwili możesz wrócić do pełnego ekranu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Aby podzielić ekran, przeciągnij drugą aplikację"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Aby przenieść aplikację,\nkliknij dwukrotnie"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksymalizuj okno aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Przywróć rozmiar okna aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimalizuj okno aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zamknij <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
<string name="handle_text" msgid="4419667835599523257">"Uchwyt aplikacji"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacji"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Wersja na komputery"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string>
<string name="more_button_text" msgid="3655388105592893530">"Więcej"</string>
<string name="float_button_text" msgid="9221657008391364581">"Pływające"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Zmień format obrazu"</string>
<string name="close_text" msgid="4986518933445178928">"Zamknij"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (wersja na komputery)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmień rozmiar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Zmień rozmiar okna do lewej"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Zmień rozmiar okna do prawej"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Zmaksymalizuj lub przywróć rozmiar okna"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksymalizuj rozmiar okna aplikacji"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Przywróć rozmiar okna"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalizuj okno aplikacji"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zamknij okno aplikacji"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Ustawienia domyślnego otwierania"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Wybierz, gdzie chcesz otwierać linki z tej aplikacji"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"W aplikacji"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 593f830e0d6e..2d9bc2bc5b80 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Esquerda a 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Lado direito em tela cheia"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Trocar o app de cima pelo de baixo"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Trocar o app da esquerda pelo da direita"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Parte superior em tela cheia"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Parte superior a 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"O menu do app está aqui"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Acesse a versão para computadores para abrir vários apps ao mesmo tempo"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Volte para a tela cheia a qualquer momento no menu do app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Fechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
<string name="handle_text" msgid="4419667835599523257">"Identificador do app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Versão para computadores"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
<string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mudar a proporção"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (versão para computadores)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionar janela para a esquerda"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionar janela para a direita"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar o tamanho da janela"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar o tamanho da janela do app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar o tamanho da janela"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar janela do app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fechar janela do app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Configurações \"Abrir por padrão\""</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escolha como abrir links da Web para este app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index fcf59163be83..765a2071a037 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% no ecrã esquerdo"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% no ecrã esquerdo"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Ecrã direito inteiro"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Trocar app superior pela inferior"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Trocar app da esquerda pela da direita"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ecrã superior inteiro"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% no ecrã superior"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% no ecrã superior"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"O menu da app pode ser encontrado aqui"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Entre na vista de computador para abrir várias apps em conjunto"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Regresse ao ecrã inteiro em qualquer altura a partir do menu da app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outra app para usar o ecrã dividido"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes\npara mover esta app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Fechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
<string name="handle_text" msgid="4419667835599523257">"Indicador da app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ícone da app"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Ecrã inteiro"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Vista de computador"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Ecrã dividido"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flutuar"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Alterar formato"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (vista de computador)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionar janela para a esquerda"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionar janela para a direita"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar tamanho da janela"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar tamanho da janela da app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar tamanho da janela"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar janela da app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fechar janela da app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Definições de Abrir por predefinição"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escolha como abrir links da Web para esta app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na app"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 593f830e0d6e..2d9bc2bc5b80 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Esquerda a 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Lado direito em tela cheia"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Trocar o app de cima pelo de baixo"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Trocar o app da esquerda pelo da direita"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Parte superior em tela cheia"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Parte superior a 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"O menu do app está aqui"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Acesse a versão para computadores para abrir vários apps ao mesmo tempo"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Volte para a tela cheia a qualquer momento no menu do app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Fechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
<string name="handle_text" msgid="4419667835599523257">"Identificador do app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Versão para computadores"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
<string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mudar a proporção"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (versão para computadores)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionar janela para a esquerda"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionar janela para a direita"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar o tamanho da janela"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar o tamanho da janela do app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar o tamanho da janela"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar janela do app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fechar janela do app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Configurações \"Abrir por padrão\""</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escolha como abrir links da Web para este app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 81db82a8c526..68d045fdbe29 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Partea stângă: 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Partea stângă: 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Partea dreaptă pe ecran complet"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Schimbă aplicația de sus cu cea de jos"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Schimbă aplicația din stânga cu cea din dreapta"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Partea de sus pe ecran complet"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Partea de sus: 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Partea de sus: 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Meniul aplicației poate fi găsit aici"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Accesează afișarea pe desktop pentru a deschide mai multe aplicații simultan"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Revino oricând la ecranul complet din meniul aplicației"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Atinge de două ori\nca să muți aplicația"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximizează <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restabilește <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizează <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Închide <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
<string name="handle_text" msgid="4419667835599523257">"Handle de aplicație"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Pictograma aplicației"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Afișare pe desktop"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Schimbă raportul de dimensiuni"</string>
<string name="close_text" msgid="4986518933445178928">"Închide"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (afișare pe desktop)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionează"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionează fereastra la stânga"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionează fereastra la dreapta"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizează sau restabilește dimensiunea ferestrei"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizează dimensiunea ferestrei aplicației"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restabilește dimensiunea ferestrei"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizează fereastra aplicației"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Închide fereastra aplicației"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Setări de deschidere în mod prestabilit"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Alege modul de deschidere a linkurilor web pentru aplicație"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"În aplicație"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index aa6f484debee..8e683b8afb79 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левый на 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Левый на 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Правый во весь экран"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Поменять местами приложения сверху и снизу"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Поменять местами приложения слева и справа"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Верхний во весь экран"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Верхний на 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхний на 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Здесь вы найдете меню приложения"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Чтобы открыть сразу несколько приложений, перейдите в режим компьютера"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Вернуться в полноэкранный режим можно из меню приложения"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Дважды нажмите, чтобы\nпереместить приложение."</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Развернуть окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Восстановить окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Свернуть окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Закрыть окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Обозначение приложения"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Значок приложения"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Версия для ПК"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string>
<string name="more_button_text" msgid="3655388105592893530">"Ещё"</string>
<string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Изменить соотношение сторон"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыть"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (версия для ПК)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Изменить размер"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Растянуть окно влево"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Растянуть окно вправо"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Развернуть окно или восстановить его размер"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Развернуть окно приложения"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Восстановить размер окна"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Свернуть окно приложения"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Закрыть окно приложения"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Настройки, регулирующие, как по умолчанию открываются ссылки"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Выберите, где будут открываться ссылки из этого приложения"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложении"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index efa978aa4505..6d548395bfac 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"වම් 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"වම් 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"දකුණු පූර්ණ තිරය"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ඉහළ යෙදුම පහළ සමග මාරු කරන්න"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"වම් යෙදුම දකුණ සමග මාරු කරන්න"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ඉහළම පූර්ණ තිරය"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ඉහළම 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ඉහළම 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්‍රතිවර්තනය කිරීමට තට්ටු කරන්න"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"යෙදුම් මෙනුව මෙතැනින් සොයා ගත හැක"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"යෙදුම් කිහිපයක් එකට විවෘත කිරීමට ඩෙස්ක්ටොප් දසුනට ඇතුළු වන්න"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"යෙදුම් මෙනුවෙන් ඕනෑම වේලාවක පූර්ණ තිරය වෙත ආපසු යන්න"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"යළි අරඹන්න"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"නැවත නොපෙන්වන්න"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"මෙම යෙදුම ගෙන යාමට\nදෙවරක් තට්ටු කරන්න"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> විහිදන්න"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ප්‍රතිසාධනය කරන්න"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> කුඩා කරන්න"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> වසන්න"</string>
<string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
<string name="handle_text" msgid="4419667835599523257">"යෙදුම් හසුරුව"</string>
<string name="app_icon_text" msgid="2823268023931811747">"යෙදුම් නිරූපකය"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ඩෙස්ක්ටොප් දසුන"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string>
<string name="more_button_text" msgid="3655388105592893530">"තව"</string>
<string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"දර්ශන අනුපාතය වෙනස් කරන්න"</string>
<string name="close_text" msgid="4986518933445178928">"වසන්න"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (ඩෙස්ක්ටොප් දසුන)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ප්‍රතිප්‍රමාණය කරන්න"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"කවුළුව වමට ප්‍රතිප්‍රමාණ කරන්න"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"කවුළුව දකුණට ප්‍රතිප්‍රමාණ කරන්න"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"කවුළු ප්‍රමාණය උපරිම කරන්න හෝ ප්‍රතිසාධනය කරන්න"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"යෙදුම් කවුළු ප්‍රමාණය උපරිම කරන්න"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"කවුළු ප්‍රමාණය ප්‍රතිසාධනය කරන්න"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"යෙදුම් කවුළුව අවම කරන්න"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"යෙදුම් කවුළුව වසන්න"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"පෙරනිමි සැකසීම් මඟින් විවෘත කරන්න"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"මෙම යෙදුම සඳහා වෙබ් සබැඳි විවෘත කරන ආකාරය තෝරා ගන්න"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"යෙදුම තුළ"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index fac26b49819a..c04b03873dc7 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ľavá – 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ľavá – 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pravá– na celú obrazovku"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Vymeniť hornú aplikáciu za dolnú"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Vymeniť ľavú aplikáciu za pravú"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Horná – na celú obrazovku"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Horná – 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Horná – 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Ponuku aplikácie nájdete tu"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Prejdite do zobrazenia v počítači a otvorte viac aplikácií naraz"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Z ponuky aplikácie sa môžete kedykoľvek vrátiť na celú obrazovku"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Rozdelenú obrazovku môžete použiť presunutím do inej aplikácie"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Túto aplikáciu\npresuniete dvojitým klepnutím"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maximalizovať <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Obnoviť <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimalizovať <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zavrieť <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
<string name="handle_text" msgid="4419667835599523257">"Rukoväť aplikácie"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikácie"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Zobrazenie v počítači"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Rozdelená obrazovka"</string>
<string name="more_button_text" msgid="3655388105592893530">"Viac"</string>
<string name="float_button_text" msgid="9221657008391364581">"Plávajúce"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Zmeniť pomer strán"</string>
<string name="close_text" msgid="4986518933445178928">"Zavrieť"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (zobrazenie v počítači)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmeniť veľkosť"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Zmeniť veľkosť okna vľavo"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Zmeniť veľkosť okna vpravo"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximalizovať alebo obnoviť veľkosť okna"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximalizovať veľkosť okna aplikácie"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Obnoviť veľkosť okna"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalizovať okno aplikácie"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zavrieť okno aplikácie"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Otvárať podľa predvolených nastavení"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Vyberte, ako sa majú v tejto aplikácii otvárať webové odkazy"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikácii"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index f8dacc4fde23..22d7bfe978cc 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Levi 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Desni v celozaslonski način"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamenjava zgornje aplikacije s spodnjo"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamenjava leve aplikacije z desno"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Zgornji v celozaslonski način"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Zgornji 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Zgornji 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Meni aplikacije najdete tukaj"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Preklopite v pogled za namizni računalnik, če želite odpreti več aplikacij hkrati"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"V meniju aplikacije se lahko kadar koli vrnete v celozaslonski način"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvakrat se dotaknite\nza premik te aplikacije"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Povečanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Obnovitev aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Pomanjšava aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zapiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
<string name="handle_text" msgid="4419667835599523257">"Identifikator aplikacije"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Pogled za namizni računalnik"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string>
<string name="more_button_text" msgid="3655388105592893530">"Več"</string>
<string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Sprememba razmerja stranic"</string>
<string name="close_text" msgid="4986518933445178928">"Zapri"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (pogled za namizni računalnik)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Spremeni velikost"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Sprememba velikosti okna na levi"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Sprememba velikosti okna na desni"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Povečava ali obnovitev velikosti okna"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Povečanje velikosti okna aplikacije"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Obnovitev velikosti okna"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Pomanjšava okna aplikacije"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zapiranje okna aplikacije"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Nastavitve privzetega odpiranja"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Izberite način odpiranja spletnih povezav za to aplikacijo"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaciji"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index c64df85dad65..9330ec3edde5 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Majtas 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Majtas 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Ekrani i plotë djathtas"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Ndërro aplikacionin lart me atë poshtë"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Ndërro aplikacionin majtas me atë djathtas"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ekrani i plotë lart"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Lart 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Lart 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Menyja e aplikacioneve mund të gjendet këtu"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Kalo te pamja e desktopit për të hapur disa aplikacione së bashku"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Kthehu tek ekrani i plotë në çdo kohë nga menyja e aplikacioneve"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Trokit dy herë për të\nlëvizur këtë aplikacion"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimizo \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Restauro \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimizo \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Mbyll \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
<string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
<string name="handle_text" msgid="4419667835599523257">"Emërtimi i aplikacionit"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona e aplikacionit"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Pamja për desktop"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
<string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
<string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ndrysho raportin e pamjes"</string>
<string name="close_text" msgid="4986518933445178928">"Mbyll"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Pamja për desktop)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ndrysho përmasat"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ndrysho përmasat e dritares në të majtë"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ndrysho përmasat e dritares në të djathtë"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimizo ose restauro madhësinë e dritares"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimizo madhësinë e dritares së aplikacionit"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restauro madhësinë e dritares"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizo dritaren e aplikacionit"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Mbyll dritaren e aplikacionit"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Hap sipas cilësimeve të parazgjedhura"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Zgjidh si do t\'i hapësh lidhjet e uebit për këtë aplikacion"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Në aplikacion"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index d2462a7282c2..5bdad159b146 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Леви екран 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Леви екран 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Режим целог екрана за доњи екран"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Замените горњу апликацију доњом"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Замените леву апликацију десном"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Режим целог екрана за горњи екран"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Горњи екран 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горњи екран 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Мени апликације можете да пронађете овде"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Уђите у приказ за рачунаре да бисте истовремено отворили више апликација"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Вратите се на цео екран било када из менија апликације"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Превуците другу апликацију да бисте користили подељени екран"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двапут додирните да бисте\nпреместили ову апликацију"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Увећајте: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Вратите: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Умањите: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Затворите: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Идентификатор апликације"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Икона апликације"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Приказ за рачунаре"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string>
<string name="more_button_text" msgid="3655388105592893530">"Још"</string>
<string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промени размеру"</string>
<string name="close_text" msgid="4986518933445178928">"Затворите"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (приказ за рачунаре)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Прилагоди"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Промените величину прозора налево"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Промените величину прозора надесно"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Увећајте или вратите величину прозора"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Увећајте величину прозора апликације"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Вратите величину прозора"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Умањите прозор апликације"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Затворите прозор апликације"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Подешавање Подразумевано отварај"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Одаберите начин отварања веб-линкова за ову апликацију"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У апликацији"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 0105e158ac08..40a0a5aa6288 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vänster 50 %"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vänster 30 %"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Helskärm på höger skärm"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Byt plats på den översta och understa appen"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Byt plats på den vänstra och högra appen"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Helskärm på övre skärm"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Övre 70 %"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Övre 50 %"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Appmenyn finns här"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Starta datorvyn för att öppna flera appar samtidigt"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Återgå till helskärm när som helst från appmenyn"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra till en annan app för att dela upp skärmen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryck snabbt två gånger\nför att flytta denna app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Utöka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Återställ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimera <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Stäng <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
<string name="handle_text" msgid="4419667835599523257">"Apphandtag"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Datorvy"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
<string name="float_button_text" msgid="9221657008391364581">"Svävande"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ändra bildformat"</string>
<string name="close_text" msgid="4986518933445178928">"Stäng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (datorvy)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ändra storlek"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ändra storlek på fönstret åt vänster"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ändra storlek på fönstret åt höger"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximera eller återställ fönsterstorleken"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximera appfönstrets storlek"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Återställ fönsterstorlek"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimera appfönstret"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Stäng appfönstret"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Inställningar för Öppna som standard"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Välj hur webblänkar ska öppnas för den här appen"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index b4415cb58b87..20429e17d0d6 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kushoto 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kushoto 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Skrini nzima ya kulia"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Badilisha nafasi ya programu ya juu na ya chini"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Badilisha nafasi ya programu ya kulia na ya kushoto"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Skrini nzima ya juu"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Juu 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Juu 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Unaweza kupata menyu ya programu hapa"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Tumia mwonekano wa kompyuta ili ufungue programu nyingi pamoja"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Rudi kwenye skrini nzima wakati wowote ukitumia menyu ya programu"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Buruta katika programu nyingine ili utumie skrini iliyogawanywa"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Gusa mara mbili ili\nusogeze programu hii"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Panua <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Rejesha <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Punguza <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Funga <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
<string name="handle_text" msgid="4419667835599523257">"Utambulisho wa programu"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Aikoni ya Programu"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Mwonekano wa Kompyuta"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string>
<string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string>
<string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Badilisha uwiano"</string>
<string name="close_text" msgid="4986518933445178928">"Funga"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Mwonekano wa Kompyuta)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Badilisha ukubwa"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Badilisha ukubwa wa dirisha kushoto"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Badilisha ukubwa wa dirisha kulia"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Panua au urejeshe ukubwa wa dirisha"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Panua ukubwa wa dirisha la programu"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Rejesha ukubwa wa dirisha"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Punguza dirisha la programu"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Funga dirisha la programu"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Fungua kwa mipangilio chaguomsingi"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Chagua jinsi ya kufungua viungo vya wavuti vya programu hii"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Kwenye programu"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index ccc9f9423e8a..74ecfdcf73a9 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"இடது புறம் 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"இடது புறம் 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"வலது புறம் முழுத் திரை"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"மேலுள்ள ஆப்ஸைக் கீழுள்ள ஆப்ஸ் கொண்டு மாற்றும்"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"இடதுபுற ஆப்ஸை வலதுபுற ஆப்ஸ் கொண்டு மாற்றும்"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"மேற்புறம் முழுத் திரை"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"மேலே 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"மேலே 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ஆப்ஸ் மெனுவை இங்கே பார்க்கலாம்"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"பல ஆப்ஸை ஒன்றாகத் திறக்க டெஸ்க்டாப் காட்சிக்குச் செல்லலாம்"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ஆப்ஸ் மெனுவிலிருந்து எப்போது வேண்டுமானாலும் முழுத்திரைக்குத் திரும்பலாம்"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"மீண்டும் தொடங்கு"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"மீண்டும் காட்டாதே"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"இந்த ஆப்ஸை நகர்த்த\nஇருமுறை தட்டவும்"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸைப் பெரிதாக்கும்"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை மீட்டெடுக்கும்"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸைச் சிறிதாக்கும்"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை மூடும்"</string>
<string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
<string name="handle_text" msgid="4419667835599523257">"ஆப்ஸ் ஹேண்டில்"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ஆப்ஸ் ஐகான்"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string>
- <string name="desktop_text" msgid="1582173066857454541">"டெஸ்க்டாப் காட்சி"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string>
<string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string>
<string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"தோற்ற விகிதத்தை மாற்று"</string>
<string name="close_text" msgid="4986518933445178928">"மூடும்"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (டெஸ்க்டாப் காட்சி)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"அளவை மாற்று"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"சாளரத்தை இடதுபுறமாக அளவு மாற்றும்"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"சாளரத்தை வலதுபுறமாக அளவு மாற்றும்"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"சாளரத்தின் அளவைப் பெரிதாக்கும்/மீட்டெடுக்கும்"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ஆப்ஸ் சாளரத்தின் அளவைப் பெரிதாக்கும்"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"சாளரத்தின் அளவை மீட்டெடுக்கும்"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ஆப்ஸ் சாளரத்தைச் சிறிதாக்கும்"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ஆப்ஸ் சாளரத்தை மூடும்"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"இயல்பாக அமைப்புகளைத் திறக்கும்"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"இந்த ஆப்ஸில் வலை இணைப்புகளைத் திறக்கும் வழிமுறையைத் தேர்வுசெய்யுங்கள்"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ஆப்ஸில்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 96ce40abb477..f46c9b628d1a 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్‌"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"పైన ఉన్న యాప్‌ను కింద ఉన్న యాప్‌తో మార్చండి"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ఎడమ వైపు ఉన్న యాప్‌ను కుడి వైపు ఉన్న యాప్‌తో మార్చండి"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్‌"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"యాప్ మెనూను ఇక్కడ పొందవచ్చు"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"పలు యాప్‌లను ఒకేసారి తెరవడానికి డెస్క్‌టాప్ వీక్షణకు ఎంటర్ అవ్వండి"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"యాప్ మెనూ నుండి ఏ సమయంలోనైనా ఫుల్ స్క్రీన్‌కు తిరిగి రండి"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"రీస్టార్ట్ చేయండి"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"మళ్లీ చూపవద్దు"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ఈ యాప్‌ను తరలించడానికి\nడబుల్-ట్యాప్ చేయండి"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను పెద్దదిగా చేయండి"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను రీస్టోర్ చేయండి"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను చిన్నదిగా చేయండి"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>‌ను మూసివేయండి"</string>
<string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
<string name="handle_text" msgid="4419667835599523257">"యాప్ హ్యాండిల్"</string>
<string name="app_icon_text" msgid="2823268023931811747">"యాప్ చిహ్నం"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string>
- <string name="desktop_text" msgid="1582173066857454541">"డెస్క్‌టాప్ వీక్షణ"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string>
<string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string>
<string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"ఆకార నిష్పత్తిని మార్చండి"</string>
<string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (డెస్క్‌టాప్ వీక్షణ)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్‌ను పెంచండి"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"సైజ్ మార్చండి"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్‌ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"విండో ఎడమ వైపునకు సైజ్‌ను మార్చండి"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"విండో కుడి వైపునకు సైజ్‌ను మార్చండి"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"విండో సైజ్‌ను మ్యాగ్జిమైజ్ చేయండి లేదా రీస్టోర్ చేయండి"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"యాప్ విండో సైజ్‌ను పెద్దదిగా చేయండి"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"విండో సైజ్‌ను రీస్టోర్ చేయండి"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"యాప్ విండోను చిన్నదిగా చేయండి"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"యాప్ విండోను మూసివేయండి"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ఆటోమేటిక్ సెట్టింగ్‌ల ద్వారా తెరవండి"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ఈ యాప్‌నకు సంబంధించిన వెబ్ లింక్‌లను ఎలా తెరవాలో ఎంచుకోండి"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"యాప్‌లో"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 16372d26977c..60632ada32bb 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ซ้าย 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ซ้าย 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"เต็มหน้าจอทางขวา"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"สลับแอปด้านบนกับด้านล่าง"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"สลับแอปด้านซ้ายกับด้านขวา"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"เต็มหน้าจอด้านบน"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ด้านบน 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ด้านบน 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ดูเมนูแอปที่นี่ได้"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"เข้าสู่มุมมองบนเดสก์ท็อปเพื่อเปิดหลายแอปพร้อมกัน"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"กลับไปที่โหมดเต็มหน้าจอได้ทุกเมื่อจากเมนูแอป"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"แตะสองครั้ง\nเพื่อย้ายแอปนี้"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"ขยาย <xliff:g id="APP_NAME">%1$s</xliff:g> ให้ใหญ่สุด"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"คืนค่า <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"ย่อ <xliff:g id="APP_NAME">%1$s</xliff:g> ให้เล็กสุด"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"ปิด <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
<string name="handle_text" msgid="4419667835599523257">"แฮนเดิลแอป"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ไอคอนแอป"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"เต็มหน้าจอ"</string>
- <string name="desktop_text" msgid="1582173066857454541">"มุมมองบนเดสก์ท็อป"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"แยกหน้าจอ"</string>
<string name="more_button_text" msgid="3655388105592893530">"เพิ่มเติม"</string>
<string name="float_button_text" msgid="9221657008391364581">"ล่องลอย"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"เปลี่ยนสัดส่วนการแสดงผล"</string>
<string name="close_text" msgid="4986518933445178928">"ปิด"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (มุมมองบนเดสก์ท็อป)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ปรับขนาด"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ปรับขนาดหน้าต่างไปทางซ้าย"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ปรับขนาดหน้าต่างไปทางขวา"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ขยายหรือคืนค่าขนาดหน้าต่าง"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ขยายขนาดหน้าต่างแอปให้ใหญ่สุด"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"คืนค่าขนาดหน้าต่าง"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ย่อหน้าต่างแอปให้เล็กสุด"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ปิดหน้าต่างแอป"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"เปิดตามการตั้งค่าเริ่มต้น"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"เลือกวิธีเปิดเว็บลิงก์สำหรับแอปนี้"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ในแอป"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index ad38be82a76a..ae6df04c255c 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Gawing 50% ang nasa kaliwa"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Gawing 30% ang nasa kaliwa"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"I-full screen ang nasa kanan"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Pagpalitin ang app sa itaas at ibaba"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Pagpalitin ang app sa kaliwa at kanan"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"I-full screen ang nasa itaas"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gawing 70% ang nasa itaas"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gawing 50% ang nasa itaas"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Makikita rito ang menu ng app"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Pumasok sa desktop view para magbukas ng maraming app nang sabay-sabay"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Bumalik sa full screen anumang oras mula sa menu ng app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Mag-drag ng isa pang app para sa split screen"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"I-double tap para\nilipat ang app na ito"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"I-maximize ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"I-restore ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"I-minimize ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Isara ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
<string name="handle_text" msgid="4419667835599523257">"Handle ng app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icon ng App"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop View"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
<string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string>
<string name="float_button_text" msgid="9221657008391364581">"Float"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Baguhin ang aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Isara"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Desktop View)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"I-resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"I-resize pakaliwa ang window"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"I-resize pakanan ang window"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"I-maximize o i-restore ang laki ng window"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"I-maximize ang laki ng window ng app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"I-restore ang laki ng window"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"I-minimize ang window ng app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Isara ang window ng app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Buksan sa pamamagitan ng mga default na setting"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Piliin kung paano magbukas ng web link para sa app na ito"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sa app"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 2bc0a960bbb5..116740ef93f7 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Solda %50"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Solda %30"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Sağda tam ekran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Üstteki uygulamayı alttaki uygulamayla değiştir"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Soldaki uygulamayı sağdakiyle değiştir"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Üstte tam ekran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Üstte %70"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Üstte %50"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Uygulama menüsünü burada bulabilirsiniz"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Birden fazla uygulamayı birlikte açmak için masaüstü görünümüne geçin"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Uygulama menüsünden dilediğiniz zaman tam ekrana dönebilirsiniz"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu uygulamayı taşımak için\niki kez dokunun"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını büyüt"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını geri yükle"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını küçült"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını kapat"</string>
<string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
<string name="handle_text" msgid="4419667835599523257">"Uygulama tanıtıcısı"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Uygulama Simgesi"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Masaüstü görünümü"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
<string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string>
<string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"En boy oranını değiştir"</string>
<string name="close_text" msgid="4986518933445178928">"Kapat"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (masaüstü görünümü)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Yeniden boyutlandır"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Pencereyi sola yeniden boyutlandır"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Pencereyi sağa yeniden boyutlandır"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Pencereyi ekranı kaplayacak şekilde büyüt veya önceki boyutuna döndür"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Uygulama penceresini ekranı kaplayacak şekilde büyüt"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pencere boyutunu geri yükle"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Uygulama penceresini küçült"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Uygulama penceresini kapat"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Varsayılan olarak açma ayarları"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Bu uygulama için web bağlantılarının nasıl açılacağını seçin"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Uygulamada"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index c1aa82e472b1..54af06762029 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ліве вікно на 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ліве вікно на 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Праве вікно на весь екран"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Поміняти місцями додатки зверху й знизу"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Поміняти місцями додатки ліворуч і праворуч"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Верхнє вікно на весь екран"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Верхнє вікно на 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхнє вікно на 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Тут ви знайдете меню додатка"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Щоб відкрити кілька додатків одночасно, перейдіть у режим робочого стола"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"З меню додатка можна будь-коли повернутися в повноекранний режим"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустити"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Більше не показувати"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двічі торкніться, щоб\nперемістити цей додаток"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Розгорнути додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Відновити додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Згорнути додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Закрити додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Дескриптор додатка"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Значок додатка"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Версія для ПК"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string>
<string name="more_button_text" msgid="3655388105592893530">"Більше"</string>
<string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Змінити формат"</string>
<string name="close_text" msgid="4986518933445178928">"Закрити"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (версія для ПК)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змінити розмір"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Змінити розмір вікна ліворуч"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Змінити розмір вікна праворуч"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Розгорнути вікно або відновити його розмір"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Розгорнути вікно додатка"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Відновити розмір вікна"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Згорнути вікно додатка"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Закрити вікно додатка"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Налаштування \"Відкривати за умовчанням\""</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Виберіть, як відкривати вебпосилання в цьому додатку"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У додатку"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 1afb48d7952c..635bc4086c0d 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"بائیں %50"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"بائیں %30"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"دائیں فل اسکرین"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"اوپری ایپ کو نیچے کے ساتھ سویپ کریں"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"بائیں ایپ کو دائیں کے ساتھ سویپ کریں"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"بالائی فل اسکرین"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"اوپر %70"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"اوپر %50"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"ایپ کا مینو یہاں پایا جا سکتا ہے"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"متعدد ایپس کو ایک ساتھ کھولنے کے لیے ڈیسک ٹاپ منظر میں داخل ہوں"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"ایپ مینو سے کسی بھی وقت فُل اسکرین پر واپس جائیں"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"اس ایپ کو منتقل کرنے کیلئے\nدو بار تھپتھپائیں"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> بڑا کریں"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> کو بحال کریں"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> چھوٹا کریں"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> بند کریں"</string>
<string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
<string name="handle_text" msgid="4419667835599523257">"ایپ ہینڈل"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ایپ کا آئیکن"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string>
- <string name="desktop_text" msgid="1582173066857454541">"ڈیسک ٹاپ منظر"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string>
<string name="more_button_text" msgid="3655388105592893530">"مزید"</string>
<string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"تناسبی شرح کو تبدیل کریں"</string>
<string name="close_text" msgid="4986518933445178928">"بند کریں"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> (ڈیسک ٹاپ منظر)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"سائز تبدیل کریں"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"دائیں طرف ونڈو کا سائز تبدیل کریں"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ونڈو کا سائز بائیں طرف تبدیل کریں"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ونڈو کا سائز زیادہ سے زیادہ یا بحال کریں"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ایپ ونڈو سائز بڑا کریں"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ونڈو سائز بحال کریں"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ایپ ونڈو چھوٹی کریں"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ایپ ونڈو بند کریں"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"بطور ڈیفالٹ ترتیبات کھولیں"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"اس ایپ کے لیے ویب لنکس کھولنے کا طریقہ منتخب کریں"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ایپ میں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 04fd4290a89f..6ce2bd859bea 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Chapda 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Chapda 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"O‘ngda to‘liq ekran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Tepadagi ilovani pastkisi bilan almashtirish"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Chap ilovani oʻngdagisi bilan almashtirish"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Tepada to‘liq ekran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Tepada 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Tepada 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Ilova menyusi shu yerda chiqadi"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Bir nechta ilovani birga ochish uchun kompyuter versiyasiga kiring"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Ilova menyusi orqali istalganda butun ekranga qaytish mumkin"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu ilovani siljitish uchun\nikki marta bosing"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Yoyish: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Tiklash: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Kichraytirish: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Yopish: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
<string name="handle_text" msgid="4419667835599523257">"Ilova identifikatori"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ilova belgisi"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Butun ekran"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Desktop versiya"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Ekranni ikkiga ajratish"</string>
<string name="more_button_text" msgid="3655388105592893530">"Yana"</string>
<string name="float_button_text" msgid="9221657008391364581">"Pufakli"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tomonlar nisbatini oʻzgartirish"</string>
<string name="close_text" msgid="4986518933445178928">"Yopish"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (desktop versiya)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Oʻlchamini oʻzgartirish"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Oyna oʻlchamini chapga oʻzgartirish"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Oyna oʻlchamini oʻngga oʻzgartirish"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Oyna oʻlchamini kengaytirish yoki asliga qaytarish"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Ilova oynasini kattartirish"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Oyna hajmini tiklash"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Ilova oynasini kichraytirish"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Ilova oynasini yopish"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Birlamchi sozlamalar asosida ochish"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Bu ilovalardagi veb havolalar qanday ochilishini tanlang"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ilovada"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 169c2b787fa7..67f80c142ea3 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Trái 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Trái 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Toàn màn hình bên phải"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Hoán đổi ứng dụng ở trên cùng với ứng dụng ở dưới cùng"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Hoán đổi ứng dụng bên trái với ứng dụng bên phải"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Toàn màn hình phía trên"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Trên 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Trên 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Bạn có thể tìm thấy trình đơn ứng dụng tại đây"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Chuyển sang chế độ xem trên máy tính để bàn để mở nhiều ứng dụng cùng lúc"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Trở về chế độ toàn màn hình bất cứ lúc nào từ trình đơn ứng dụng"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Kéo một ứng dụng khác vào để chia đôi màn hình"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Nhấn đúp để\ndi chuyển ứng dụng này"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Phóng to <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Khôi phục <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Thu nhỏ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Đóng <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
<string name="handle_text" msgid="4419667835599523257">"Ô điều khiển ứng dụng"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Biểu tượng ứng dụng"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Chế độ xem trên máy tính để bàn"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string>
<string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string>
<string name="float_button_text" msgid="9221657008391364581">"Nổi"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Thay đổi tỷ lệ khung hình"</string>
<string name="close_text" msgid="4986518933445178928">"Đóng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Chế độ xem trên máy tính để bàn)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Đổi kích thước"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Đổi kích thước và chuyển cửa sổ sang trái"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Đổi kích thước và chuyển cửa sổ sang phải"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Phóng to hoặc khôi phục kích thước cửa sổ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Phóng to kích thước cửa sổ ứng dụng"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Khôi phục kích thước cửa sổ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Thu nhỏ cửa sổ ứng dụng"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Đóng cửa sổ ứng dụng"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Mở các chế độ cài đặt theo mặc định"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Chọn cách mở đường liên kết trang web cho ứng dụng này"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Trong ứng dụng"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 942734ad0849..b29711df14b9 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左侧 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"左侧 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"右侧全屏"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"将顶部应用与底部应用互换"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"将左侧应用与右侧应用互换"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"顶部全屏"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"顶部 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"顶部 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"您可以在此处找到应用菜单"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"进入桌面版视图可同时打开多个应用"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"随时从应用菜单返回全屏模式"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一个应用,即可使用分屏模式"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"重启"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不再显示"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"点按两次\n即可移动此应用"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"将“<xliff:g id="APP_NAME">%1$s</xliff:g>”窗口最大化"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"恢复“<xliff:g id="APP_NAME">%1$s</xliff:g>”窗口大小"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"将“<xliff:g id="APP_NAME">%1$s</xliff:g>”窗口最小化"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"关闭“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
<string name="back_button_text" msgid="1469718707134137085">"返回"</string>
<string name="handle_text" msgid="4419667835599523257">"应用手柄"</string>
<string name="app_icon_text" msgid="2823268023931811747">"应用图标"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string>
- <string name="desktop_text" msgid="1582173066857454541">"桌面版视图"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"分屏"</string>
<string name="more_button_text" msgid="3655388105592893530">"更多"</string>
<string name="float_button_text" msgid="9221657008391364581">"悬浮"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"更改宽高比"</string>
<string name="close_text" msgid="4986518933445178928">"关闭"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g>(桌面版视图)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"调整大小"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"调整窗口大小并贴靠左侧"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"调整窗口大小并贴靠右侧"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"将窗口最大化或恢复大小"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"将应用窗口最大化"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"恢复窗口大小"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"将应用窗口最小化"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"关闭应用窗口"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"默认打开设置"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"选择如何打开此应用中的网页链接"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在此应用内"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index f89792235c78..74e6b5c1e491 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左邊 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"左邊 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"右邊全螢幕"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"調換上下應用程式"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"調換左右應用程式"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"頂部全螢幕"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"頂部 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"頂部 50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"你可在這裡找到應用程式選單"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"進入桌面電腦檢視模式以同時開啟多個應用程式"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"你可隨時從應用程式選單返回全螢幕"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一個應用程式即可分割螢幕"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕按兩下\n即可移動此應用程式"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"將 <xliff:g id="APP_NAME">%1$s</xliff:g> 放到最大"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"還原 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"將 <xliff:g id="APP_NAME">%1$s</xliff:g> 縮到最細"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"閂 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"返去"</string>
<string name="handle_text" msgid="4419667835599523257">"應用程式控點"</string>
<string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
- <string name="desktop_text" msgid="1582173066857454541">"桌面電腦檢視模式"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string>
<string name="more_button_text" msgid="3655388105592893530">"更多"</string>
<string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更長寬比"</string>
<string name="close_text" msgid="4986518933445178928">"關閉"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (桌面電腦檢視模式)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"將視窗移去左邊調整大小"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"將視窗移去右邊調整大小"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"將視窗放到最大或者還原視窗大小"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"將應用程式視窗放到最大"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"還原視窗大細"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"將應用程式視窗縮到最細"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"關閉應用程式視窗"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"採用預設設定打開"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"選擇此應用程式開啟網絡連結的方式"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在應用程式內"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 3c6abec3c08c..575b217229bd 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"以 50% 的螢幕空間顯示左側畫面"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"以 30% 的螢幕空間顯示左側畫面"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"以全螢幕顯示右側畫面"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"將頂端與底部的應用程式對調"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"將左側與右側的應用程式對調"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"以全螢幕顯示頂端畫面"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"以 70% 的螢幕空間顯示頂端畫面"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"以 50% 的螢幕空間顯示頂端畫面"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"你可以在這裡查看應用程式選單"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"進入電腦檢視畫面可以同時開啟多個應用程式"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"你隨時可以從應用程式選單返回全螢幕模式"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖進另一個應用程式即可使用分割畫面模式"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕觸兩下即可\n移動這個應用程式"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"最大化「<xliff:g id="APP_NAME">%1$s</xliff:g>」視窗"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"還原「<xliff:g id="APP_NAME">%1$s</xliff:g>」視窗"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"最小化「<xliff:g id="APP_NAME">%1$s</xliff:g>」視窗"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"關閉「<xliff:g id="APP_NAME">%1$s</xliff:g>」"</string>
<string name="back_button_text" msgid="1469718707134137085">"返回"</string>
<string name="handle_text" msgid="4419667835599523257">"應用程式控制代碼"</string>
<string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
- <string name="desktop_text" msgid="1582173066857454541">"電腦檢視畫面"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string>
<string name="more_button_text" msgid="3655388105592893530">"更多"</string>
<string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更顯示比例"</string>
<string name="close_text" msgid="4986518933445178928">"關閉"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (電腦檢視畫面)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"調整應用程式視窗大小並向左貼齊"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"調整應用程式視窗大小並向右貼齊"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"將視窗最大化或還原大小"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"最大化應用程式視窗"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"還原視窗大小"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"最小化應用程式視窗"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"關閉應用程式視窗"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"開啟連結的預設設定"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"選擇如何開啟這個應用程式的網頁連結"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"使用應用程式"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index b304299a6c18..30403cd21862 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kwesokunxele ngo-50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kwesokunxele ngo-30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Isikrini esigcwele esingakwesokudla"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Shintshanisa i-app ephezulu ngengaphansi"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Shintshanisa i-app engakwesokunxele naleyo engakwesokudla"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Isikrini esigcwele esiphezulu"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Okuphezulu okungu-70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Okuphezulu okungu-50%"</string>
@@ -102,7 +100,8 @@
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"Imenyu ye-app ingatholakala lapha"</string>
- <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"Faka ukubuka kwedeskithophu ukuze uvule ama-app amaningi ndawonye"</string>
+ <!-- no translation found for windowing_desktop_mode_image_button_education_tooltip (7171915734817051666) -->
+ <skip />
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Buyela esikrinini esigcwele noma nini ukusuka kumenyu ye-app"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string>
@@ -115,19 +114,16 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Thepha kabili ukuze\nuhambise le-app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Khulisa i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Buyisela i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Nciphisa i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Vala i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
<string name="handle_text" msgid="4419667835599523257">"Inkomba ye-App"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Isithonjana Se-app"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Ukubuka Kwedeskithophu"</string>
+ <!-- no translation found for desktop_text (9058641752519570266) -->
+ <skip />
<string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string>
<string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string>
<string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string>
@@ -140,7 +136,8 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Shintsha ukubukeka kwesilinganiselo"</string>
<string name="close_text" msgid="4986518933445178928">"Vala"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Ukubuka Kwedeskithophu)"</string>
+ <!-- no translation found for desktop_mode_app_header_chip_text (7617377295944971651) -->
+ <skip />
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Shintsha usayizi"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string>
@@ -158,14 +155,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Shintsha usayizi wewindi ngakwesokunxele"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Shintsha usayizi wewindi ngakwesokudla"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Khulisa noma buyisela usayizi wewindi"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Khulisa usayizi wewindi le-app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Buyisela usayizi wewindi"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Nciphisa iwindi le-app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Vala iwindi le-app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Vula amasethingi ngokuzenzakalela"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Khetha indlela yokuvula amalinki ewebhu ale app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ku-app"</string>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 32660e8fca27..e1bf6638a9b2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -291,6 +291,9 @@
<!-- Corner radius for expanded view drop target -->
<dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen>
<dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding_top">60dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding_bottom">24dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding_horizontal">48dp</dimen>
<!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
<dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
<!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
@@ -437,9 +440,6 @@
<!-- Height of button (32dp) + 2 * margin (5dp each). -->
<dimen name="freeform_decor_caption_height">42dp</dimen>
- <!-- Height of desktop mode caption for freeform tasks. -->
- <dimen name="desktop_mode_freeform_decor_caption_height">40dp</dimen>
-
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
@@ -528,17 +528,21 @@
<!-- The radius of the Maximize menu shadow. -->
<dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
- <!-- The width of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
+ <!-- The width of the handle menu in desktop mode plus the 2dp added for padding to account for
+ pill elevation. -->
+ <dimen name="desktop_mode_handle_menu_width">218dp</dimen>
- <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
- additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding.
- 52*3 + 52*4 + (4-1)*2 + 4 = 374 -->
- <dimen name="desktop_mode_handle_menu_height">374dp</dimen>
+ <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each plus
+ additional actions pill 208dp plus 2dp spacing between them plus 4dp top padding
+ plus 2dp bottom padding: 52*3 + 52*4 + (4-1)*2 + 4 + 2 = 376 -->
+ <dimen name="desktop_mode_handle_menu_height">376dp</dimen>
<!-- The elevation set on the handle menu pills. -->
<dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
+ <!-- The padding added to account for the handle menu's pills' elevation. -->
+ <dimen name="desktop_mode_handle_menu_pill_elevation_padding">2dp</dimen>
+
<!-- The height of the handle menu's "App Info" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
@@ -573,7 +577,10 @@
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
<!-- The radius of the caption menu icon. -->
- <dimen name="desktop_mode_caption_icon_radius">32dp</dimen>
+ <dimen name="desktop_mode_caption_icon_radius">24dp</dimen>
+
+ <!-- The radius of the icon in the header menu's app info pill. -->
+ <dimen name="desktop_mode_handle_menu_icon_radius">32dp</dimen>
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
@@ -619,13 +626,13 @@
<dimen name="desktop_mode_header_app_chip_ripple_inset_vertical">4dp</dimen>
<!-- The corner radius of the windowing actions pill buttons's ripple drawable -->
- <dimen name="desktop_mode_handle_menu_windowing_action_ripple_radius">24dp</dimen>
+ <dimen name="desktop_mode_handle_menu_icon_button_ripple_radius">24dp</dimen>
<!-- The horizontal/vertical inset to apply to the ripple drawable effect of windowing
actions pill central buttons -->
- <dimen name="desktop_mode_handle_menu_windowing_action_ripple_inset_base">2dp</dimen>
+ <dimen name="desktop_mode_handle_menu_icon_button_ripple_inset_base">2dp</dimen>
<!-- The horizontal/vertical vertical inset to apply to the ripple drawable effect of windowing
actions pill edge buttons -->
- <dimen name="desktop_mode_handle_menu_windowing_action_ripple_inset_shift">4dp</dimen>
+ <dimen name="desktop_mode_handle_menu_icon_button_ripple_inset_shift">4dp</dimen>
<!-- The corner radius of the minimize button's ripple drawable -->
<dimen name="desktop_mode_header_minimize_ripple_radius">18dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 2179128df300..5ef83826840b 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -226,7 +226,7 @@
<string name="windowing_app_handle_education_tooltip">The app menu can be found here</string>
<!-- App handle education tooltip text for tooltip pointing to windowing image button -->
- <string name="windowing_desktop_mode_image_button_education_tooltip">Enter desktop view to open multiple apps together</string>
+ <string name="windowing_desktop_mode_image_button_education_tooltip">Enter desktop windowing to open multiple apps together</string>
<!-- App handle education tooltip text for tooltip pointing to app chip -->
<string name="windowing_desktop_mode_exit_education_tooltip">Return to full screen anytime from the app menu</string>
@@ -293,7 +293,7 @@
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
<string name="fullscreen_text">Fullscreen</string>
<!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
- <string name="desktop_text">Desktop View</string>
+ <string name="desktop_text">Desktop windowing</string>
<!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
<string name="split_screen_text">Split Screen</string>
<!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
@@ -319,7 +319,7 @@
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
<string name="collapse_menu_text">Close Menu</string>
<!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] -->
- <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop View)</string>
+ <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop windowing)</string>
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
<!-- Maximize menu snap buttons string. -->
@@ -348,7 +348,7 @@
<!-- Accessibility action replacement for caption handle app chip buttons [CHAR LIMIT=NONE] -->
<string name="app_handle_chip_accessibility_announce">Open Menu</string>
<!-- Accessibility action replacement for caption handle menu buttons [CHAR LIMIT=NONE] -->
- <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop View">%1$s</xliff:g></string>
+ <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop windowing">%1$s</xliff:g></string>
<!-- Accessibility action replacement for maximize menu enter snap left button [CHAR LIMIT=NONE] -->
<string name="maximize_menu_talkback_action_snap_left_text">Resize window to left</string>
<!-- Accessibility action replacement for maximize menu enter snap right button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
index 01d2201a5a0c..8bcbd2a3fc9f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
@@ -22,4 +22,11 @@ package com.android.wm.shell.shared;
public class ShellSharedConstants {
public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION =
"extra_shell_can_hand_off_animation";
+
+ /**
+ * Defines the max screen width or height in dp for a device to be considered a small tablet.
+ *
+ * @see android.view.WindowManager#LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ */
+ public static final int SMALL_TABLET_MAX_EDGE_DP = 960;
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt
new file mode 100644
index 000000000000..9bf56b075112
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.shared
+
+import android.graphics.Typeface
+import android.widget.TextView
+import com.android.wm.shell.Flags
+
+/**
+ * Utility class to apply a specified typeface to a [TextView].
+ *
+ * This class provides a method, [setTypeface],
+ * to easily set a pre-defined font family and style to a given [TextView].
+ */
+class TypefaceUtils {
+
+ enum class FontFamily(val value: String) {
+ GSF_DISPLAY_LARGE("variable-display-large"),
+ GSF_DISPLAY_MEDIUM("variable-display-medium"),
+ GSF_DISPLAY_SMALL("variable-display-small"),
+ GSF_HEADLINE_LARGE("variable-headline-large"),
+ GSF_HEADLINE_MEDIUM("variable-headline-medium"),
+ GSF_HEADLINE_SMALL("variable-headline-small"),
+ GSF_TITLE_LARGE("variable-title-large"),
+ GSF_TITLE_MEDIUM("variable-title-medium"),
+ GSF_TITLE_SMALL("variable-title-small"),
+ GSF_LABEL_LARGE("variable-label-large"),
+ GSF_LABEL_MEDIUM("variable-label-medium"),
+ GSF_LABEL_SMALL("variable-label-small"),
+ GSF_BODY_LARGE("variable-body-large"),
+ GSF_BODY_MEDIUM("variable-body-medium"),
+ GSF_BODY_SMALL("variable-body-small"),
+ GSF_DISPLAY_LARGE_EMPHASIZED("variable-display-large-emphasized"),
+ GSF_DISPLAY_MEDIUM_EMPHASIZED("variable-display-medium-emphasized"),
+ GSF_DISPLAY_SMALL_EMPHASIZED("variable-display-small-emphasized"),
+ GSF_HEADLINE_LARGE_EMPHASIZED("variable-headline-large-emphasized"),
+ GSF_HEADLINE_MEDIUM_EMPHASIZED("variable-headline-medium-emphasized"),
+ GSF_HEADLINE_SMALL_EMPHASIZED("variable-headline-small-emphasized"),
+ GSF_TITLE_LARGE_EMPHASIZED("variable-title-large-emphasized"),
+ GSF_TITLE_MEDIUM_EMPHASIZED("variable-title-medium-emphasized"),
+ GSF_TITLE_SMALL_EMPHASIZED("variable-title-small-emphasized"),
+ GSF_LABEL_LARGE_EMPHASIZED("variable-label-large-emphasized"),
+ GSF_LABEL_MEDIUM_EMPHASIZED("variable-label-medium-emphasized"),
+ GSF_LABEL_SMALL_EMPHASIZED("variable-label-small-emphasized"),
+ GSF_BODY_LARGE_EMPHASIZED("variable-body-large-emphasized"),
+ GSF_BODY_MEDIUM_EMPHASIZED("variable-body-medium-emphasized"),
+ GSF_BODY_SMALL_EMPHASIZED("variable-body-small-emphasized"),
+ }
+
+ companion object {
+ /**
+ * Sets the typeface of the provided [textView] to the specified [fontFamily] and [fontStyle].
+ *
+ * The typeface is only applied to the [TextView] when [Flags.enableGsf] is `true`.
+ * If [Flags.enableGsf] is `false`, this method has no effect.
+ *
+ * @param textView The [TextView] to which the typeface should be applied. If `null`, this method does nothing.
+ * @param fontFamily The desired [FontFamily] for the [TextView].
+ * @param fontStyle The desired font style (e.g., [Typeface.NORMAL], [Typeface.BOLD], [Typeface.ITALIC]). Defaults to [Typeface.NORMAL].
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun setTypeface(
+ textView: TextView?,
+ fontFamily: FontFamily,
+ fontStyle: Int = Typeface.NORMAL,
+ ) {
+ if (!Flags.enableGsf()) return
+ textView?.typeface = Typeface.create(fontFamily.name, fontStyle)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
index f479da051e06..ad2671b8135d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
@@ -26,6 +26,8 @@ import android.view.WindowInsets
import android.view.WindowManager
import kotlin.math.max
+import com.android.wm.shell.shared.ShellSharedConstants.SMALL_TABLET_MAX_EDGE_DP
+
/** Contains device configuration used for positioning bubbles on the screen. */
data class DeviceConfig(
val isLargeScreen: Boolean,
@@ -38,7 +40,6 @@ data class DeviceConfig(
companion object {
private const val LARGE_SCREEN_MIN_EDGE_DP = 600
- private const val SMALL_TABLET_MAX_EDGE_DP = 960
@JvmStatic
fun create(context: Context, windowManager: WindowManager): DeviceConfig {
@@ -49,19 +50,29 @@ data class DeviceConfig(
or WindowInsets.Type.displayCutout())
val windowBounds = windowMetrics.bounds
val config: Configuration = context.resources.configuration
- val isLargeScreen = config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP
- val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp)
- val isSmallTablet = isLargeScreen && largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP
val isLandscape = context.resources.configuration.orientation == ORIENTATION_LANDSCAPE
val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
return DeviceConfig(
- isLargeScreen = isLargeScreen,
- isSmallTablet = isSmallTablet,
+ isLargeScreen = isLargeScreen(config),
+ isSmallTablet = isSmallTablet(context),
isLandscape = isLandscape,
isRtl = isRtl,
windowBounds = windowBounds,
insets = insets
)
}
+
+ @JvmStatic
+ fun isSmallTablet(context: Context): Boolean {
+ val config: Configuration = context.resources.configuration
+ if (!isLargeScreen(config)) {
+ return false
+ }
+ val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp)
+ return largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP
+ }
+
+ private fun isLargeScreen(config: Configuration) =
+ config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index 1a80b0f29aa9..afeaf70c9d62 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -111,47 +111,28 @@ class DragZoneFactory(
/** Updates all dimensions after a configuration change. */
fun onConfigurationUpdated() {
- dismissDragZoneSize =
- if (deviceConfig.isSmallTablet) {
- context.resolveDimension(R.dimen.drag_zone_dismiss_fold)
- } else {
- context.resolveDimension(R.dimen.drag_zone_dismiss_tablet)
- }
- bubbleDragZoneTabletSize = context.resolveDimension(R.dimen.drag_zone_bubble_tablet)
- bubbleDragZoneFoldableSize = context.resolveDimension(R.dimen.drag_zone_bubble_fold)
- fullScreenDragZoneWidth = context.resolveDimension(R.dimen.drag_zone_full_screen_width)
- fullScreenDragZoneHeight = context.resolveDimension(R.dimen.drag_zone_full_screen_height)
- desktopWindowDragZoneWidth =
- context.resolveDimension(R.dimen.drag_zone_desktop_window_width)
- desktopWindowDragZoneHeight =
- context.resolveDimension(R.dimen.drag_zone_desktop_window_height)
- desktopWindowFromExpandedViewDragZoneWidth =
- context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_width)
- desktopWindowFromExpandedViewDragZoneHeight =
- context.resolveDimension(R.dimen.drag_zone_desktop_window_expanded_view_height)
- splitFromBubbleDragZoneHeight =
- context.resolveDimension(R.dimen.drag_zone_split_from_bubble_height)
- splitFromBubbleDragZoneWidth =
- context.resolveDimension(R.dimen.drag_zone_split_from_bubble_width)
- hSplitFromExpandedViewDragZoneWidth =
- context.resolveDimension(R.dimen.drag_zone_h_split_from_expanded_view_width)
- vSplitFromExpandedViewDragZoneWidth =
- context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_width)
- vSplitFromExpandedViewDragZoneHeightTablet =
- context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_tablet)
- vSplitFromExpandedViewDragZoneHeightFoldTall =
- context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall)
- vSplitFromExpandedViewDragZoneHeightFoldShort =
- context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short)
- fullScreenDropTargetPadding =
- context.resolveDimension(R.dimen.drop_target_full_screen_padding)
- desktopWindowDropTargetPaddingSmall =
- context.resolveDimension(R.dimen.drop_target_desktop_window_padding_small)
- desktopWindowDropTargetPaddingLarge =
- context.resolveDimension(R.dimen.drop_target_desktop_window_padding_large)
-
- // TODO b/393172431: Use the shared xml resources once we can easily access them from
+ // TODO b/396539130: Use the shared xml resources once we can easily access them from
// launcher
+ dismissDragZoneSize =
+ if (deviceConfig.isSmallTablet) 140.dpToPx() else 200.dpToPx()
+ bubbleDragZoneTabletSize = 200.dpToPx()
+ bubbleDragZoneFoldableSize = 140.dpToPx()
+ fullScreenDragZoneWidth = 512.dpToPx()
+ fullScreenDragZoneHeight = 44.dpToPx()
+ desktopWindowDragZoneWidth = 880.dpToPx()
+ desktopWindowDragZoneHeight = 300.dpToPx()
+ desktopWindowFromExpandedViewDragZoneWidth = 200.dpToPx()
+ desktopWindowFromExpandedViewDragZoneHeight = 350.dpToPx()
+ splitFromBubbleDragZoneHeight = 100.dpToPx()
+ splitFromBubbleDragZoneWidth = 60.dpToPx()
+ hSplitFromExpandedViewDragZoneWidth = 60.dpToPx()
+ vSplitFromExpandedViewDragZoneWidth = 200.dpToPx()
+ vSplitFromExpandedViewDragZoneHeightTablet = 285.dpToPx()
+ vSplitFromExpandedViewDragZoneHeightFoldTall = 150.dpToPx()
+ vSplitFromExpandedViewDragZoneHeightFoldShort = 100.dpToPx()
+ fullScreenDropTargetPadding = 20.dpToPx()
+ desktopWindowDropTargetPaddingSmall = 100.dpToPx()
+ desktopWindowDropTargetPaddingLarge = 130.dpToPx()
expandedViewDropTargetWidth = 364.dpToPx()
expandedViewDropTargetHeight = 578.dpToPx()
expandedViewDropTargetPaddingBottom = 108.dpToPx()
@@ -322,6 +303,7 @@ class DragZoneFactory(
val isVerticalSplit = deviceConfig.isSmallTablet == deviceConfig.isLandscape
return if (isVerticalSplit) {
when (splitScreenModeChecker.getSplitScreenMode()) {
+ SplitScreenMode.UNSUPPORTED -> emptyList()
SplitScreenMode.SPLIT_50_50,
SplitScreenMode.NONE ->
listOf(
@@ -379,6 +361,7 @@ class DragZoneFactory(
}
} else {
when (splitScreenModeChecker.getSplitScreenMode()) {
+ SplitScreenMode.UNSUPPORTED -> emptyList()
SplitScreenMode.SPLIT_50_50,
SplitScreenMode.NONE ->
listOf(
@@ -472,6 +455,7 @@ class DragZoneFactory(
// vertical split drag zones are aligned with the full screen drag zone width
val splitZoneLeft = windowBounds.right / 2 - fullScreenDragZoneWidth / 2
when (splitScreenModeChecker.getSplitScreenMode()) {
+ SplitScreenMode.UNSUPPORTED -> emptyList()
SplitScreenMode.SPLIT_50_50,
SplitScreenMode.NONE ->
listOf(
@@ -579,7 +563,8 @@ class DragZoneFactory(
NONE,
SPLIT_50_50,
SPLIT_10_90,
- SPLIT_90_10
+ SPLIT_90_10,
+ UNSUPPORTED
}
fun getSplitScreenMode(): SplitScreenMode
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
index 5c2590849dd2..651e776891db 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -33,7 +33,6 @@ import com.android.wm.shell.shared.R
class DropTargetManager(
private val context: Context,
private val container: FrameLayout,
- private val isLayoutRtl: Boolean,
private val dragZoneChangedListener: DragZoneChangedListener,
) {
@@ -41,6 +40,7 @@ class DropTargetManager(
private val dropTargetView = DropTargetView(context)
private var animator: ValueAnimator? = null
private var morphRect: RectF = RectF(0f, 0f, 0f, 0f)
+ private val isLayoutRtl = container.isLayoutRtl
private companion object {
const val MORPH_ANIM_DURATION = 250L
@@ -73,16 +73,22 @@ class DropTargetManager(
val newDragZone = state.getMatchingDragZone(x = x, y = y)
state.currentDragZone = newDragZone
if (oldDragZone != newDragZone) {
- dragZoneChangedListener.onDragZoneChanged(from = oldDragZone, to = newDragZone)
+ dragZoneChangedListener.onDragZoneChanged(
+ draggedObject = state.draggedObject,
+ from = oldDragZone,
+ to = newDragZone
+ )
updateDropTarget()
}
}
/** Called when the drag ended. */
fun onDragEnded() {
+ val dropState = state ?: return
startFadeAnimation(from = dropTargetView.alpha, to = 0f) {
container.removeView(dropTargetView)
}
+ dragZoneChangedListener.onDragEnded(dropState.currentDragZone)
state = null
}
@@ -134,7 +140,7 @@ class DropTargetManager(
/** Stores the current drag state. */
private inner class DragState(
private val dragZones: List<DragZone>,
- draggedObject: DraggedObject
+ val draggedObject: DraggedObject
) {
val initialDragZone =
if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) {
@@ -155,7 +161,10 @@ class DropTargetManager(
fun onInitialDragZoneSet(dragZone: DragZone)
/** Called when the object was dragged to a different drag zone. */
- fun onDragZoneChanged(from: DragZone, to: DragZone)
+ fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone)
+
+ /** Called when the drag has ended with the zone it ended in. */
+ fun onDragEnded(zone: DragZone)
}
private fun Animator.doOnEnd(onEnd: () -> Unit) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
index 2bb6cf4ec3aa..73277310ffe4 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
+import android.util.TypedValue
import android.view.View
import com.android.wm.shell.shared.R
@@ -37,14 +38,21 @@ class DropTargetView(context: Context) : View(context) {
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
style = Paint.Style.STROKE
- strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat()
+ strokeWidth = 1.dpToPx()
}
- private val cornerRadius = context.resources.getDimensionPixelSize(
- R.dimen.drop_target_radius).toFloat()
+ private val cornerRadius = 28.dpToPx()
private val rect = RectF(0f, 0f, 0f, 0f)
+ // TODO b/396539130: Use shared xml resources once we can access them in launcher
+ private fun Int.dpToPx() =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this.toFloat(),
+ context.resources.displayMetrics
+ )
+
override fun onDraw(canvas: Canvas) {
canvas.save()
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint)
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 9ea0532f9450..529203f7ded2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -19,13 +19,11 @@ package com.android.wm.shell.shared.desktopmode
import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.app.TaskInfo
import android.content.Context
-import android.content.pm.ActivityInfo
-import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
-import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
-import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
import android.content.pm.PackageManager
import android.window.DesktopModeFlags
import com.android.internal.R
+import com.android.internal.policy.DesktopModeCompatUtils
+import java.util.function.Supplier
/**
* Class to decide whether to apply app compat policies in desktop mode.
@@ -37,9 +35,11 @@ class DesktopModeCompatPolicy(private val context: Context) {
private val pkgManager: PackageManager
get() = context.getPackageManager()
private val defaultHomePackage: String?
- get() = pkgManager.getHomeActivities(ArrayList())?.packageName
+ get() = defaultHomePackageSupplier?.get()
+ ?: pkgManager.getHomeActivities(ArrayList())?.packageName
private val packageInfoCache = mutableMapOf<String, Boolean>()
+ var defaultHomePackageSupplier: Supplier<String?>? = null
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
@@ -49,33 +49,28 @@ class DesktopModeCompatPolicy(private val context: Context) {
*/
fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
- task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent)
+ task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent,
+ task.userId)
- fun isTopActivityExemptFromDesktopWindowing(packageName: String?,
- numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
+ fun isTopActivityExemptFromDesktopWindowing(
+ packageName: String?,
+ numActivities: Int,
+ isTopActivityNoDisplay: Boolean,
+ isActivityStackTransparent: Boolean,
+ userId: Int
+ ) =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue &&
((isSystemUiTask(packageName) ||
isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) ||
(isTransparentTask(isActivityStackTransparent, numActivities) &&
- hasFullscreenTransparentPermission(packageName))) &&
+ hasFullscreenTransparentPermission(packageName, userId))) &&
!isTopActivityNoDisplay)
- /**
- * Whether the caption insets should be excluded from configuration for system to handle.
- *
- * The treatment is enabled when all the of the following is true:
- * * Any flags to forcibly consume caption insets are enabled.
- * * Top activity have configuration coupled with insets.
- * * Task is not resizeable or [ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS]
- * is enabled.
- */
+ /** @see DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds */
fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean =
- DesktopModeFlags.EXCLUDE_CAPTION_FROM_APP_BOUNDS.isTrue
- && isAnyForceConsumptionFlagsEnabled()
- && taskInfo.topActivityInfo?.let {
- isInsetsCoupledWithConfiguration(it) && (!taskInfo.isResizeable || it.isChangeEnabled(
- OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
- ))
+ taskInfo.topActivityInfo?.let {
+ DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds(it, taskInfo.isResizeable,
+ taskInfo.appCompatTaskInfo.hasOptOutEdgeToEdge())
} ?: false
/**
@@ -91,16 +86,17 @@ class DesktopModeCompatPolicy(private val context: Context) {
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
// Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission.
- private fun hasFullscreenTransparentPermission(packageName: String?): Boolean {
+ private fun hasFullscreenTransparentPermission(packageName: String?, userId: Int): Boolean {
if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) {
if (packageName == null) {
return false
}
- return packageInfoCache.getOrPut(packageName) {
+ return packageInfoCache.getOrPut("$userId@$packageName") {
try {
- val packageInfo = pkgManager.getPackageInfo(
+ val packageInfo = pkgManager.getPackageInfoAsUser(
packageName,
- PackageManager.GET_PERMISSIONS
+ PackageManager.GET_PERMISSIONS,
+ userId
)
packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true
} catch (e: PackageManager.NameNotFoundException) {
@@ -118,12 +114,4 @@ class DesktopModeCompatPolicy(private val context: Context) {
*/
private fun isPartOfDefaultHomePackageOrNoHomeAvailable(packageName: String?) =
defaultHomePackage == null || (packageName != null && packageName == defaultHomePackage)
-
- private fun isAnyForceConsumptionFlagsEnabled(): Boolean =
- DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue
- || DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue
-
- private fun isInsetsCoupledWithConfiguration(info: ActivityInfo): Boolean =
- !(info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
- || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED))
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 2e33253b5e09..ed5e0c608675 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.shared.desktopmode;
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
+import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE;
+import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper.enableBubbleToFullscreen;
import android.annotation.NonNull;
@@ -224,7 +226,7 @@ public class DesktopModeStatus {
/**
* Return {@code true} if the current device can host desktop sessions on its internal display.
*/
- public static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
+ private static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
}
@@ -269,6 +271,29 @@ public class DesktopModeStatus {
}
/**
+ * Check to see if a display should have desktop mode enabled or not. Internal
+ * and external displays have separate logic.
+ */
+ public static boolean isDesktopModeSupportedOnDisplay(Context context, Display display) {
+ if (!canEnterDesktopMode(context)) {
+ return false;
+ }
+ if (display.getType() == Display.TYPE_INTERNAL) {
+ return canInternalDisplayHostDesktops(context);
+ }
+
+ // TODO (b/395014779): Change this to use WM API
+ if ((display.getType() == Display.TYPE_EXTERNAL
+ || display.getType() == Display.TYPE_OVERLAY)
+ && enableDisplayContentModeManagement()) {
+ final WindowManager wm = context.getSystemService(WindowManager.class);
+ return wm != null && wm.shouldShowSystemDecors(display.getDisplayId());
+ }
+
+ return false;
+ }
+
+ /**
* Returns whether the multiple desktops feature is enabled for this device (both backend and
* frontend implementations).
*/
@@ -341,8 +366,11 @@ public class DesktopModeStatus {
if (!enforceDeviceRestrictions()) {
return true;
}
- final boolean desktopModeSupported = isDesktopModeSupported(context)
- && canInternalDisplayHostDesktops(context);
+ // If projected display is enabled, #canInternalDisplayHostDesktops is no longer a
+ // requirement.
+ final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue()
+ ? isDesktopModeSupported(context) : (isDesktopModeSupported(context)
+ && canInternalDisplayHostDesktops(context));
final boolean desktopModeSupportedByDevOptions =
Flags.enableDesktopModeThroughDevOption()
&& isDesktopModeDevOptionSupported(context);
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
index 0954b52ff151..ac54ac7a9d99 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
@@ -48,12 +48,14 @@ abstract class ManageWindowsViewContainer(
lateinit var menuView: ManageWindowsView
/** Creates the base menu view and fills it with icon views. */
- fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
+ fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot?>>,
onIconClickListener: ((Int) -> Unit),
onOutsideClickListener: (() -> Unit)): ManageWindowsView {
- val bitmapList = snapshotList.map { (index, snapshot) ->
- index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
- }
+ val bitmapList = snapshotList
+ .filter { it.second != null }
+ .map { (index, snapshot) ->
+ index to Bitmap.wrapHardwareBuffer(snapshot!!.hardwareBuffer, snapshot.colorSpace)
+ }
return createAndShowMenuView(
bitmapList,
onIconClickListener,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
index 5018fdb615da..8e78686ac13d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
@@ -83,7 +83,11 @@ public class SizeChangeAnimation {
private static final int ANIMATION_RESOLUTION = 1000;
public SizeChangeAnimation(Rect startBounds, Rect endBounds) {
- mAnimation = buildContainerAnimation(startBounds, endBounds);
+ this(startBounds, endBounds, 1f);
+ }
+
+ public SizeChangeAnimation(Rect startBounds, Rect endBounds, float initialScale) {
+ mAnimation = buildContainerAnimation(startBounds, endBounds, initialScale);
mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds);
}
@@ -167,7 +171,8 @@ public class SizeChangeAnimation {
}
/** Animation for the whole container (snapshot is inside this container). */
- private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds) {
+ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds,
+ float initialScale) {
final long duration = ANIMATION_RESOLUTION;
boolean growing = endBounds.width() - startBounds.width()
+ endBounds.height() - startBounds.height() >= 0;
@@ -180,15 +185,27 @@ public class SizeChangeAnimation {
final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
scaleAnim.setDuration(scalePeriod);
+ long scaleStartOffset = 0;
if (!growing) {
- scaleAnim.setStartOffset(duration - scalePeriod);
+ scaleStartOffset = duration - scalePeriod;
}
+ scaleAnim.setStartOffset(scaleStartOffset);
animSet.addAnimation(scaleAnim);
+
+ if (initialScale != 1f) {
+ final Animation initialScaleAnim = new ScaleAnimation(initialScale, 1f, initialScale,
+ 1f);
+ initialScaleAnim.setDuration(scalePeriod);
+ initialScaleAnim.setStartOffset(scaleStartOffset);
+ animSet.addAnimation(initialScaleAnim);
+ }
+
final Animation translateAnim = new TranslateAnimation(startBounds.left,
endBounds.left, startBounds.top, endBounds.top);
translateAnim.setDuration(duration);
animSet.addAnimation(translateAnim);
Rect startClip = new Rect(startBounds);
+ startClip.scale(initialScale);
Rect endClip = new Rect(endBounds);
startClip.offsetTo(0, 0);
endClip.offsetTo(0, 0);
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 d1c7f7d7dcad..7f8cfaeb9c03 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
@@ -28,6 +28,7 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
+import static com.android.systemui.Flags.predictiveBackDelayWmTransition;
import static com.android.window.flags.Flags.unifyBackNavigationTransition;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -431,6 +432,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
public void onThresholdCrossed() {
mThresholdCrossed = true;
+ BackTouchTracker activeTracker = getActiveTracker();
+ if (predictiveBackDelayWmTransition() && activeTracker != null && mActiveCallback == null
+ && mBackGestureStarted) {
+ startBackNavigation(activeTracker);
+ }
// There was no focus window when calling startBackNavigation, still pilfer pointers so
// the next focus window won't receive motion events.
if (mBackNavigationInfo == null && mReceivedNullNavigationInfo) {
@@ -442,7 +448,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (!shouldDispatchToAnimator && mActiveCallback != null) {
mCurrentTracker.updateStartLocation();
- tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent());
if (mBackNavigationInfo != null && !isAppProgressGenerationAllowed()) {
tryPilferPointers();
}
@@ -488,9 +494,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (swipeEdge == EDGE_NONE) {
// start animation immediately for non-gestural sources (without ACTION_MOVE
// events)
- mThresholdCrossed = true;
+ if (!predictiveBackDelayWmTransition()) {
+ mThresholdCrossed = true;
+ }
mPointersPilfered = true;
onGestureStarted(touchX, touchY, swipeEdge);
+ if (predictiveBackDelayWmTransition()) {
+ onThresholdCrossed();
+ }
mShouldStartOnNextMoveEvent = false;
} else {
mShouldStartOnNextMoveEvent = true;
@@ -544,14 +555,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mPostCommitAnimationInProgress = false;
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
startSystemAnimation();
- } else if (touchTracker == mCurrentTracker) {
- // Only start the back navigation if no other gesture is being processed. Otherwise,
- // the back navigation will fall back to legacy back event injection.
- startBackNavigation(mCurrentTracker);
+ } else if (!predictiveBackDelayWmTransition()) {
+ startBackNavigation(touchTracker);
}
}
private void startBackNavigation(@NonNull BackTouchTracker touchTracker) {
+ if (touchTracker != mCurrentTracker) {
+ // Only start the back navigation if no other gesture is being processed. Otherwise,
+ // the back navigation will fall back to legacy back event injection.
+ return;
+ }
try {
startLatencyTracking();
if (mBackAnimationAdapter != null
@@ -590,7 +604,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
// App is handling back animation. Cancel system animation latency tracking.
cancelLatencyTracking();
- tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent());
if (!isAppProgressGenerationAllowed()) {
tryPilferPointers();
}
@@ -1027,7 +1041,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
() -> mShellExecutor.execute(this::onBackAnimationFinished));
if (mApps.length >= 1) {
- BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
+ BackMotionEvent startEvent = mCurrentTracker.createStartEvent();
dispatchOnBackStarted(mActiveCallback, startEvent);
if (startEvent.getSwipeEdge() == EDGE_NONE) {
// TODO(b/373544911): onBackStarted is dispatched here so that
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index d9489287ff42..4f30a052de80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -375,7 +375,7 @@ public class Bubble implements BubbleViewProvider {
user,
icon,
BubbleType.TYPE_APP,
- getAppBubbleKeyForApp(intent.getPackage(), user),
+ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user),
mainExecutor, bgExecutor);
}
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 be2240286ab1..912de813cf59 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
@@ -1604,7 +1604,7 @@ public class BubbleController implements ConfigurationChangeListener,
@Nullable BubbleTransitions.DragData dragData) {
if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return;
Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
- ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId);
+ ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - taskId=%s", taskInfo.taskId);
BubbleBarLocation location = null;
if (dragData != null) {
location =
@@ -2643,6 +2643,11 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleData.setSelectedBubbleAndExpandStack(bubbleToSelect);
}
+ private void moveBubbleToFullscreen(String key) {
+ Bubble b = mBubbleData.getBubbleInStackWithKey(key);
+ mBubbleTransitions.startDraggedBubbleIconToFullscreen(b);
+ }
+
private boolean isDeviceLocked() {
return !mIsStatusBarShade;
}
@@ -2929,6 +2934,11 @@ public class BubbleController implements ConfigurationChangeListener,
}
});
}
+
+ @Override
+ public void moveBubbleToFullscreen(String key) {
+ mMainExecutor.execute(() -> mController.moveBubbleToFullscreen(key));
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index abcdb7e70cec..226ad57e4c66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -1236,7 +1236,6 @@ public class BubbleData {
/** @return the bubble in the stack that matches the provided key. */
@Nullable
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getBubbleInStackWithKey(String key) {
return getBubbleWithPredicate(mBubbles, b -> b.getKey().equals(key));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2c2451cab999..cbd1e9671825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -238,7 +238,6 @@ public class BubbleExpandedView extends LinearLayout {
mContext.createContextAsUser(
mBubble.getUser(), Context.CONTEXT_RESTRICTED);
Intent fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
PendingIntent pi = PendingIntent.getActivity(
context,
/* requestCode= */ 0,
@@ -467,6 +466,11 @@ public class BubbleExpandedView extends LinearLayout {
new BubbleTaskViewListener.Callback() {
@Override
public void onTaskCreated() {
+ // The taskId is saved to use for removeTask,
+ // preventing appearance in recent tasks.
+ mTaskId = ((BubbleTaskViewListener) mCurrentTaskViewListener)
+ .getTaskId();
+
setContentVisibility(true);
}
@@ -602,6 +606,10 @@ public class BubbleExpandedView extends LinearLayout {
updateManageButtonIfExists();
}
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
/**
* Updates the size and visuals of the pointer if {@link #mPointerView} is initialized.
* Does nothing otherwise.
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 33f1b94bac73..03d6b0a8075d 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
@@ -103,7 +103,9 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
private int mManageButtonHeight;
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
- private int mBubbleBarExpandedViewDropTargetPadding;
+ private int mBarExpViewDropTargetPaddingTop;
+ private int mBarExpViewDropTargetPaddingBottom;
+ private int mBarExpViewDropTargetPaddingHorizontal;
private PointF mRestingStackPosition;
@@ -136,6 +138,11 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
+ /** Returns the device config being used. */
+ public DeviceConfig getCurrentConfig() {
+ return mDeviceConfig;
+ }
+
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
BubbleStackView.RelativeStackPosition prevStackPosition = null;
@@ -168,8 +175,12 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width),
mPositionRect.width() - 2 * mExpandedViewPadding
);
- mBubbleBarExpandedViewDropTargetPadding = res.getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_drop_target_padding);
+ mBarExpViewDropTargetPaddingTop = res.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_drop_target_padding_top);
+ mBarExpViewDropTargetPaddingBottom = res.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_drop_target_padding_bottom);
+ mBarExpViewDropTargetPaddingHorizontal = res.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_drop_target_padding_horizontal);
if (mShowingInBubbleBar) {
mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth;
@@ -981,8 +992,15 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
public Rect getBubbleBarExpandedViewDropTargetBounds(boolean onLeft) {
Rect bounds = new Rect();
getBubbleBarExpandedViewBounds(onLeft, false, bounds);
- bounds.inset(mBubbleBarExpandedViewDropTargetPadding,
- mBubbleBarExpandedViewDropTargetPadding);
+ // Drop target bounds are based on expanded view bounds with some padding added
+ int leftPadding = onLeft ? 0 : mBarExpViewDropTargetPaddingHorizontal;
+ int rightPadding = onLeft ? mBarExpViewDropTargetPaddingHorizontal : 0;
+ bounds.inset(
+ leftPadding,
+ mBarExpViewDropTargetPaddingTop,
+ rightPadding,
+ mBarExpViewDropTargetPaddingBottom
+ );
return bounds;
}
}
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 92724178cf84..dd5a23aae7f9 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
@@ -91,6 +91,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
@@ -1319,7 +1320,7 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.bringToFront();
}
- // TODO: Create ManageMenuView and move setup / animations there
+ // TODO (b/402196554) : Create ManageMenuView and move setup / animations there
private void setUpManageMenu() {
if (mManageMenu != null) {
removeView(mManageMenu);
@@ -1377,6 +1378,22 @@ public class BubbleStackView extends FrameLayout
mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
+ View fullscreenView = mManageMenu.findViewById(
+ R.id.bubble_manage_menu_fullscreen_container);
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ fullscreenView.setVisibility(VISIBLE);
+ fullscreenView.setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null && expandedView.getTaskView() != null) {
+ expandedView.getTaskView().moveToFullscreen();
+ }
+ });
+ } else {
+ fullscreenView.setVisibility(GONE);
+ }
+
// The menu itself should respect locale direction so the icons are on the correct side.
mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
addView(mManageMenu);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
index 63d713495177..9c20e3af9ab4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
@@ -130,7 +130,6 @@ public class BubbleTaskViewListener implements TaskView.Listener {
mContext.createContextAsUser(
mBubble.getUser(), Context.CONTEXT_RESTRICTED);
Intent fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
// First try get pending intent from the bubble
PendingIntent pi = mBubble.getPendingIntent();
if (pi == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 338ffe76e6ea..c1841c707a2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -30,6 +30,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
@@ -112,6 +113,11 @@ public class BubbleTransitions {
return convert;
}
+ /** Starts a transition that converts a dragged bubble icon to a full screen task. */
+ public BubbleTransition startDraggedBubbleIconToFullscreen(Bubble bubble) {
+ return new DraggedBubbleIconToFullscreen(bubble);
+ }
+
/**
* Plucks the task-surface out of an ancestor view while making the view invisible. This helper
* attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent).
@@ -155,28 +161,26 @@ public class BubbleTransitions {
* Information about the task when it is being dragged to a bubble
*/
public static class DragData {
- private final Rect mBounds;
private final WindowContainerTransaction mPendingWct;
private final boolean mReleasedOnLeft;
+ private final float mTaskScale;
+ private final float mCornerRadius;
+ private final PointF mDragPosition;
/**
- * @param bounds bounds of the dragged task when the drag was released
- * @param wct pending operations to be applied when finishing the drag
* @param releasedOnLeft true if the bubble was released in the left drop target
+ * @param taskScale the scale of the task when it was dragged to bubble
+ * @param cornerRadius the corner radius of the task when it was dragged to bubble
+ * @param dragPosition the position of the task when it was dragged to bubble
+ * @param wct pending operations to be applied when finishing the drag
*/
- public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct,
- boolean releasedOnLeft) {
- mBounds = bounds;
+ public DragData(boolean releasedOnLeft, float taskScale, float cornerRadius,
+ @Nullable PointF dragPosition, @Nullable WindowContainerTransaction wct) {
mPendingWct = wct;
mReleasedOnLeft = releasedOnLeft;
- }
-
- /**
- * @return bounds of the dragged task when the drag was released
- */
- @Nullable
- public Rect getBounds() {
- return mBounds;
+ mTaskScale = taskScale;
+ mCornerRadius = cornerRadius;
+ mDragPosition = dragPosition != null ? dragPosition : new PointF(0, 0);
}
/**
@@ -193,6 +197,27 @@ public class BubbleTransitions {
public boolean isReleasedOnLeft() {
return mReleasedOnLeft;
}
+
+ /**
+ * @return the scale of the task when it was dragged to bubble
+ */
+ public float getTaskScale() {
+ return mTaskScale;
+ }
+
+ /**
+ * @return the corner radius of the task when it was dragged to bubble
+ */
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
+ /**
+ * @return position of the task when it was dragged to bubble
+ */
+ public PointF getDragPosition() {
+ return mDragPosition;
+ }
}
/**
@@ -347,29 +372,27 @@ public class BubbleTransitions {
}
mFinishCb = finishCallback;
- if (mDragData != null && mDragData.getBounds() != null) {
- // Override start bounds with the dragged task bounds
- mStartBounds.set(mDragData.getBounds());
+ if (mDragData != null) {
+ mStartBounds.offsetTo((int) mDragData.getDragPosition().x,
+ (int) mDragData.getDragPosition().y);
+ startTransaction.setScale(mSnapshot, mDragData.getTaskScale(),
+ mDragData.getTaskScale());
+ startTransaction.setCornerRadius(mSnapshot, mDragData.getCornerRadius());
}
// Now update state (and talk to launcher) in parallel with snapshot stuff
mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true,
/* showInShade= */ false);
+ final int left = mStartBounds.left - info.getRoot(0).getOffset().x;
+ final int top = mStartBounds.top - info.getRoot(0).getOffset().y;
+ startTransaction.setPosition(mTaskLeash, left, top);
startTransaction.show(mSnapshot);
// Move snapshot to root so that it remains visible while task is moved to taskview
startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash());
- startTransaction.setPosition(mSnapshot,
- mStartBounds.left - info.getRoot(0).getOffset().x,
- mStartBounds.top - info.getRoot(0).getOffset().y);
+ startTransaction.setPosition(mSnapshot, left, top);
startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE);
- BubbleBarExpandedView bbev = mBubble.getBubbleBarExpandedView();
- if (bbev != null) {
- // Corners get reset during the animation. Add them back
- startTransaction.setCornerRadius(mSnapshot, bbev.getRestingCornerRadius());
- }
-
startTransaction.apply();
mTaskViewTransitions.onExternalDone(transition);
@@ -416,6 +439,8 @@ public class BubbleTransitions {
private void playAnimation(boolean animate) {
final TaskViewTaskController tv = mBubble.getTaskView().getController();
final SurfaceControl.Transaction startT = new SurfaceControl.Transaction();
+ // Set task position to 0,0 as it will be placed inside the TaskView
+ startT.setPosition(mTaskLeash, 0, 0);
mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT,
(ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct);
@@ -424,10 +449,12 @@ public class BubbleTransitions {
}
if (animate) {
- mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> {
- mFinishCb.onTransitionFinished(mFinishWct);
- mFinishCb = null;
- });
+ float startScale = mDragData != null ? mDragData.getTaskScale() : 1f;
+ mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot, mTaskLeash,
+ () -> {
+ mFinishCb.onTransitionFinished(mFinishWct);
+ mFinishCb = null;
+ });
} else {
startT.apply();
mFinishCb.onTransitionFinished(mFinishWct);
@@ -585,8 +612,7 @@ public class BubbleTransitions {
mTaskLeash = taskChg.getLeash();
mRootLeash = info.getRoot(0).getLeash();
- SurfaceControl dest =
- mBubble.getBubbleBarExpandedView().getViewRootImpl().getSurfaceControl();
+ SurfaceControl dest = getExpandedView(mBubble).getViewRootImpl().getSurfaceControl();
final Runnable onPlucked = () -> {
// Need to remove the taskview AFTER applying the startTransaction because
// it isn't synchronized.
@@ -596,12 +622,12 @@ public class BubbleTransitions {
mBubbleData.setExpanded(false /* expanded */);
};
if (dest != null) {
- pluck(mTaskLeash, mBubble.getBubbleBarExpandedView(), dest,
+ pluck(mTaskLeash, getExpandedView(mBubble), dest,
taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x,
taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y,
- mBubble.getBubbleBarExpandedView().getCornerRadius(), startTransaction,
+ getCornerRadius(mBubble), startTransaction,
onPlucked);
- mBubble.getBubbleBarExpandedView().post(() -> mTransitions.dispatchTransition(
+ getExpandedView(mBubble).post(() -> mTransitions.dispatchTransition(
mTransition, info, startTransaction, finishTransaction, finishCallback,
null));
} else {
@@ -617,10 +643,135 @@ public class BubbleTransitions {
@Override
public void continueCollapse() {
mBubble.cleanupTaskView();
- if (mTaskLeash == null) return;
+ if (mTaskLeash == null || !mTaskLeash.isValid()) return;
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.reparent(mTaskLeash, mRootLeash);
t.apply();
}
+
+ private View getExpandedView(@NonNull Bubble bubble) {
+ if (bubble.getBubbleBarExpandedView() != null) {
+ return bubble.getBubbleBarExpandedView();
+ }
+ return bubble.getExpandedView();
+ }
+
+ private float getCornerRadius(@NonNull Bubble bubble) {
+ if (bubble.getBubbleBarExpandedView() != null) {
+ return bubble.getBubbleBarExpandedView().getCornerRadius();
+ }
+ return bubble.getExpandedView().getCornerRadius();
+ }
+ }
+
+ /**
+ * A transition that converts a dragged bubble icon to a full screen window.
+ *
+ * <p>This transition assumes that the bubble is invisible so it is simply sent to front.
+ */
+ class DraggedBubbleIconToFullscreen implements Transitions.TransitionHandler, BubbleTransition {
+
+ IBinder mTransition;
+ final Bubble mBubble;
+
+ DraggedBubbleIconToFullscreen(Bubble bubble) {
+ mBubble = bubble;
+ bubble.setPreparingTransition(this);
+ WindowContainerToken token = bubble.getTaskView().getTaskInfo().getToken();
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setAlwaysOnTop(token, false);
+ wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(token, /* onTop= */ true);
+ wct.setHidden(token, false);
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false);
+ mTaskViewTransitions.enqueueExternal(bubble.getTaskView().getController(), () -> {
+ mTransition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, this);
+ return mTransition;
+ });
+ }
+
+ @Override
+ public void skip() {
+ mBubble.setPreparingTransition(null);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mTransition != transition) {
+ return false;
+ }
+
+ final TaskViewTaskController taskViewTaskController =
+ mBubble.getTaskView().getController();
+ if (taskViewTaskController == null) {
+ mTaskViewTransitions.onExternalDone(transition);
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ TransitionInfo.Change change = findTransitionChange(info);
+ if (change == null) {
+ Slog.w(TAG, "Expected a TaskView transition to front but didn't find "
+ + "one, cleaning up the task view");
+ taskViewTaskController.setTaskNotFound();
+ mTaskViewTransitions.onExternalDone(transition);
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+ mRepository.remove(taskViewTaskController);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ taskViewTaskController.notifyTaskRemovalStarted(mBubble.getTaskView().getTaskInfo());
+ mTaskViewTransitions.onExternalDone(transition);
+ return true;
+ }
+
+ private TransitionInfo.Change findTransitionChange(TransitionInfo info) {
+ TransitionInfo.Change result = null;
+ WindowContainerToken token = mBubble.getTaskView().getTaskInfo().getToken();
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() == null) {
+ continue;
+ }
+ if (change.getMode() != TRANSIT_TO_FRONT) {
+ continue;
+ }
+ if (!token.equals(change.getTaskInfo().token)) {
+ continue;
+ }
+ result = change;
+ break;
+ }
+ return result;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ if (!aborted) {
+ return;
+ }
+ mTransition = null;
+ mTaskViewTransitions.onExternalDone(transition);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index ae1b4077098d..079edb3ea8ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -58,4 +58,6 @@ interface IBubbles {
oneway void showExpandedView() = 14;
oneway void showDropTarget(in boolean show, in @nullable BubbleBarLocation location) = 15;
+
+ oneway void moveBubbleToFullscreen(in String key) = 16;
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 52f20646fb4a..fa22a3961002 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -580,6 +580,7 @@ public class BubbleBarAnimationHelper {
public void animateConvert(BubbleViewProvider expandedBubble,
@NonNull SurfaceControl.Transaction startT,
@NonNull Rect origBounds,
+ float origScale,
@NonNull SurfaceControl snapshot,
@NonNull SurfaceControl taskLeash,
@Nullable Runnable afterAnimation) {
@@ -599,7 +600,7 @@ public class BubbleBarAnimationHelper {
new SizeChangeAnimation(
new Rect(origBounds.left - position.x, origBounds.top - position.y,
origBounds.right - position.x, origBounds.bottom - position.y),
- new Rect(0, 0, size.getWidth(), size.getHeight()));
+ new Rect(0, 0, size.getWidth(), size.getHeight()), origScale);
sca.initialize(bbev, taskLeash, snapshot, startT);
Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> {
@@ -614,6 +615,7 @@ public class BubbleBarAnimationHelper {
bbev.setSurfaceZOrderedOnTop(true);
a.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION);
+ a.setInterpolator(Interpolators.EMPHASIZED);
a.start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 34259bfb7aaa..9d4f904e55d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -21,7 +21,11 @@ import android.view.MotionEvent
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.DismissView
+import com.android.wm.shell.shared.bubbles.DragZoneFactory
+import com.android.wm.shell.shared.bubbles.DraggedObject
+import com.android.wm.shell.shared.bubbles.DropTargetManager
import com.android.wm.shell.shared.bubbles.RelativeTouchListener
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject
@@ -33,6 +37,8 @@ class BubbleBarExpandedViewDragController(
private val animationHelper: BubbleBarAnimationHelper,
private val bubblePositioner: BubblePositioner,
private val pinController: BubbleExpandedViewPinController,
+ private val dropTargetManager: DropTargetManager?,
+ private val dragZoneFactory: DragZoneFactory?,
@get:VisibleForTesting val dragListener: DragListener,
) {
@@ -97,7 +103,21 @@ class BubbleBarExpandedViewDragController(
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
if (expandedView.isAnimating) return false
- pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
+ if (dropTargetManager != null && dragZoneFactory != null) {
+ val draggedObject = DraggedObject.ExpandedView(
+ if (bubblePositioner.isBubbleBarOnLeft) {
+ BubbleBarLocation.LEFT
+ } else {
+ BubbleBarLocation.RIGHT
+ }
+ )
+ dropTargetManager.onDragStarted(
+ draggedObject,
+ dragZoneFactory.createSortedDragZones(draggedObject)
+ )
+ } else {
+ pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
+ }
isDragged = true
return true
}
@@ -117,7 +137,11 @@ class BubbleBarExpandedViewDragController(
expandedView.translationX = expandedViewInitialTranslationX + dx
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
- pinController.onDragUpdate(ev.rawX, ev.rawY)
+ if (dropTargetManager != null) {
+ dropTargetManager.onDragUpdated(ev.rawX.toInt(), ev.rawY.toInt())
+ } else {
+ pinController.onDragUpdate(ev.rawX, ev.rawY)
+ }
}
override fun onUp(
@@ -140,7 +164,11 @@ class BubbleBarExpandedViewDragController(
private fun finishDrag() {
if (!isStuckToDismiss) {
- pinController.onDragEnd()
+ if (dropTargetManager != null) {
+ dropTargetManager.onDragEnded()
+ } else {
+ pinController.onDragEnd()
+ }
dragListener.onReleased(inDismiss = false)
animationHelper.animateToRestPosition()
dismissView.hide()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 677c21c96f4b..3997412ab459 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -27,6 +27,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceControl;
import android.view.TouchDelegate;
@@ -48,9 +49,14 @@ import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
import com.android.wm.shell.shared.bubbles.BaseBubblePinController;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.DragZone;
+import com.android.wm.shell.shared.bubbles.DragZoneFactory;
+import com.android.wm.shell.shared.bubbles.DraggedObject;
+import com.android.wm.shell.shared.bubbles.DropTargetManager;
import kotlin.Unit;
@@ -76,6 +82,10 @@ public class BubbleBarLayerView extends FrameLayout
private final BubbleEducationViewController mEducationViewController;
private final View mScrimView;
private final BubbleExpandedViewPinController mBubbleExpandedViewPinController;
+ @Nullable
+ private DropTargetManager mDropTargetManager = null;
+ @Nullable
+ private DragZoneFactory mDragZoneFactory = null;
@Nullable
private BubbleViewProvider mExpandedBubble;
@@ -123,8 +133,72 @@ public class BubbleBarLayerView extends FrameLayout
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(new LocationChangeListener());
-
+ LocationChangeListener locationChangeListener = new LocationChangeListener();
+ mBubbleExpandedViewPinController.setListener(locationChangeListener);
+
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ mDropTargetManager = new DropTargetManager(context, this,
+ new DropTargetManager.DragZoneChangedListener() {
+ private DragZone mLastBubbleLocationDragZone = null;
+ private BubbleBarLocation mInitialLocation = null;
+ @Override
+ public void onDragEnded(@NonNull DragZone zone) {
+ if (mExpandedBubble == null || !(mExpandedBubble instanceof Bubble)) {
+ Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble);
+ return;
+ }
+ if (zone instanceof DragZone.FullScreen) {
+ ((Bubble) mExpandedBubble).getTaskView().moveToFullscreen();
+ // Make sure location change listener is updated with the initial
+ // location -- even if we "switched sides" during the drag, since
+ // we've ended up in fullscreen, the location shouldn't change.
+ locationChangeListener.onRelease(mInitialLocation);
+ } else if (zone instanceof DragZone.Bubble.Left) {
+ locationChangeListener.onRelease(BubbleBarLocation.LEFT);
+ } else if (zone instanceof DragZone.Bubble.Right) {
+ locationChangeListener.onRelease(BubbleBarLocation.RIGHT);
+ }
+ }
+
+ @Override
+ public void onInitialDragZoneSet(@NonNull DragZone dragZone) {
+ mInitialLocation = dragZone instanceof DragZone.Bubble.Left
+ ? BubbleBarLocation.LEFT
+ : BubbleBarLocation.RIGHT;
+ locationChangeListener.onStart(mInitialLocation);
+ }
+
+ @Override
+ public void onDragZoneChanged(@NonNull DraggedObject draggedObject,
+ @NonNull DragZone from, @NonNull DragZone to) {
+ final boolean isBubbleLeft = to instanceof DragZone.Bubble.Left;
+ final boolean isBubbleRight = to instanceof DragZone.Bubble.Right;
+ if ((isBubbleLeft || isBubbleRight)
+ && to != mLastBubbleLocationDragZone) {
+ mLastBubbleLocationDragZone = to;
+ locationChangeListener.onChange(isBubbleLeft
+ ? BubbleBarLocation.LEFT
+ : BubbleBarLocation.RIGHT);
+
+ }
+ }
+ });
+ // TODO - currently only fullscreen is supported, should enable for split & desktop
+ mDragZoneFactory = new DragZoneFactory(context, mPositioner.getCurrentConfig(),
+ new DragZoneFactory.SplitScreenModeChecker() {
+ @NonNull
+ @Override
+ public SplitScreenMode getSplitScreenMode() {
+ return SplitScreenMode.NONE;
+ }
+ },
+ new DragZoneFactory.DesktopWindowModeChecker() {
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+ });
+ }
setOnClickListener(view -> hideModalOrCollapse());
}
@@ -272,6 +346,8 @@ public class BubbleBarLayerView extends FrameLayout
mAnimationHelper,
mPositioner,
mBubbleExpandedViewPinController,
+ mDropTargetManager,
+ mDragZoneFactory,
dragListener);
addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
@@ -348,13 +424,13 @@ public class BubbleBarLayerView extends FrameLayout
* @param startT A transaction with first-frame work. this *will* be applied here!
*/
public void animateConvert(@NonNull SurfaceControl.Transaction startT,
- @NonNull Rect startBounds, @NonNull SurfaceControl snapshot, SurfaceControl taskLeash,
- Runnable animFinish) {
+ @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot,
+ SurfaceControl taskLeash, Runnable animFinish) {
if (!mIsExpanded || mExpandedBubble == null) {
throw new IllegalStateException("Can't animateExpand without expanded state");
}
- mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, snapshot, taskLeash,
- animFinish);
+ mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, startScale, snapshot,
+ taskLeash, animFinish);
}
/**
@@ -372,9 +448,9 @@ public class BubbleBarLayerView extends FrameLayout
bubble.cleanupViews(!inTransition);
endAction.run();
};
- if (mBubbleData.getBubbles().isEmpty()) {
- // we're removing the last bubble. collapse the expanded view and cleanup bubble views
- // at the end.
+ if (mBubbleData.getBubbles().isEmpty() || inTransition) {
+ // If we are removing the last bubble or removing the current bubble via transition,
+ // collapse the expanded view and clean up bubbles at the end.
collapse(cleanUp);
} else {
cleanUp.run();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index b7761ec75782..69009fd1606a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -28,9 +28,9 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.app.animation.Interpolators;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import java.util.ArrayList;
@@ -263,7 +263,7 @@ class BubbleBarMenuViewController {
}
));
- if (Flags.enableBubbleAnything() || Flags.enableBubbleToFullscreen()) {
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
menuActions.add(new BubbleBarMenuView.MenuAction(
Icon.createWithResource(resources,
R.drawable.desktop_mode_ic_handle_menu_fullscreen),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
index 0d0bc9be72b3..9fefb515a539 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
@@ -25,7 +25,8 @@ import com.android.wm.shell.ShellTaskOrganizer
object ComponentUtils {
/** Retrieves the package name from an [Intent]. */
@JvmStatic
- fun getPackageName(intent: Intent?): String? = intent?.component?.packageName
+ fun getPackageName(intent: Intent?): String? =
+ intent?.component?.packageName ?: intent?.`package`
/** Retrieves the package name from a [PendingIntent]. */
@JvmStatic
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index dd2050a5fd5d..f73788486d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -440,11 +440,18 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
statsToken);
}
- // In case of a hide, the statsToken should not been send yet (as the animation
- // is still ongoing). It will be sent at the end of the animation
- boolean hideAnimOngoing = !mImeRequestedVisible && mAnimation != null;
- setVisibleDirectly(mImeRequestedVisible || mAnimation != null,
- hideAnimOngoing ? null : statsToken);
+ boolean hideAnimOngoing;
+ boolean reportVisible;
+ if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+ hideAnimOngoing = false;
+ reportVisible = mImeRequestedVisible;
+ } else {
+ // In case of a hide, the statsToken should not been send yet (as the animation
+ // is still ongoing). It will be sent at the end of the animation.
+ hideAnimOngoing = !mImeRequestedVisible && mAnimation != null;
+ reportVisible = mImeRequestedVisible || mAnimation != null;
+ }
+ setVisibleDirectly(reportVisible, hideAnimOngoing ? null : statsToken);
}
}
@@ -628,7 +635,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
+ " showing:" + (mAnimationDirection == DIRECTION_SHOW));
}
if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
- setAnimating(true);
+ setAnimating(true /* imeAnimationOngoing */);
}
int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
@@ -678,7 +685,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (!android.view.inputmethod.Flags.refactorInsetsController()) {
dispatchEndPositioning(mDisplayId, mCancelled, t);
} else if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
- setAnimating(false);
+ setAnimating(false /* imeAnimationOngoing */);
}
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
ImeTracker.forLogging().onProgress(mStatsToken,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt
new file mode 100644
index 000000000000..8751b65dce94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common
+
+import android.app.ActivityManager
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.view.Display.DEFAULT_DISPLAY
+import android.window.WindowContainerTransaction
+import com.android.window.flags.Flags
+
+/** Creates home intent **/
+class HomeIntentProvider(
+ private val context: Context,
+) {
+ fun addLaunchHomePendingIntent(
+ wct: WindowContainerTransaction, displayId: Int, userId: Int? = null
+ ) {
+ val userHandle =
+ if (userId != null) UserHandle.of(userId) else UserHandle.of(ActivityManager.getCurrentUser())
+
+ val launchHomeIntent = Intent(Intent.ACTION_MAIN).apply {
+ if (displayId != DEFAULT_DISPLAY) {
+ addCategory(Intent.CATEGORY_SECONDARY_HOME)
+ } else {
+ addCategory(Intent.CATEGORY_HOME)
+ }
+ }
+ val options = ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FULLSCREEN
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ launchDisplayId = displayId
+ }
+ }
+ val pendingIntent = PendingIntent.getActivityAsUser(
+ context,
+ /* requestCode= */ 0,
+ launchHomeIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ /* options= */ null,
+ userHandle,
+ )
+ wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle())
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index c4696d5f44f4..a8e6b593f20d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -20,17 +20,14 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL;
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.os.RemoteException;
import android.util.Slog;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.shared.TransactionPool;
-import com.android.wm.shell.transition.LegacyTransitions;
import java.util.ArrayList;
@@ -87,25 +84,6 @@ public final class SyncTransactionQueue {
}
/**
- * Queues a legacy transition to be sent serially to WM
- */
- public void queue(LegacyTransitions.ILegacyTransition transition,
- @WindowManager.TransitionType int type, WindowContainerTransaction wct) {
- if (wct.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty");
- return;
- }
- SyncCallback cb = new SyncCallback(transition, type, wct);
- synchronized (mQueue) {
- if (DEBUG) Slog.d(TAG, "Queueing up legacy transition " + wct);
- mQueue.add(cb);
- if (mQueue.size() == 1) {
- cb.send();
- }
- }
- }
-
- /**
* Queues a sync transaction only if there are already sync transaction(s) queued or in flight.
* Otherwise just returns without queueing.
* @return {@code true} if queued, {@code false} if not.
@@ -168,17 +146,9 @@ public final class SyncTransactionQueue {
private class SyncCallback extends WindowContainerTransactionCallback {
int mId = -1;
final WindowContainerTransaction mWCT;
- final LegacyTransitions.LegacyTransition mLegacyTransition;
SyncCallback(WindowContainerTransaction wct) {
mWCT = wct;
- mLegacyTransition = null;
- }
-
- SyncCallback(LegacyTransitions.ILegacyTransition legacyTransition,
- @WindowManager.TransitionType int type, WindowContainerTransaction wct) {
- mWCT = wct;
- mLegacyTransition = new LegacyTransitions.LegacyTransition(type, legacyTransition);
}
// Must be sychronized on mQueue
@@ -194,12 +164,7 @@ public final class SyncTransactionQueue {
}
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
try {
- if (mLegacyTransition != null) {
- mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
- mLegacyTransition.getAdapter(), this, mWCT);
- } else {
- mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
- }
+ mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
} catch (RuntimeException e) {
Slog.e(TAG, "Send failed", e);
// Finish current sync callback immediately.
@@ -228,18 +193,10 @@ public final class SyncTransactionQueue {
if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId);
mQueue.remove(this);
onTransactionReceived(t);
- if (mLegacyTransition != null) {
- try {
- mLegacyTransition.getSyncCallback().onTransactionReady(mId, t);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e);
- }
- } else {
- ProtoLog.v(WM_SHELL,
- "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id);
- t.apply();
- t.close();
- }
+ ProtoLog.v(WM_SHELL,
+ "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id);
+ t.apply();
+ t.close();
if (!mQueue.isEmpty()) {
mQueue.get(0).send();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 214e6ad455a1..aeef211ae3f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -143,6 +143,9 @@ public class PipBoundsState {
*/
public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect();
+ private final List<OnPipComponentChangedListener> mOnPipComponentChangedListeners =
+ new ArrayList<>();
+
// the size of the current bounds relative to the max size spec
private float mBoundsScale;
@@ -156,9 +159,7 @@ public class PipBoundsState {
// Update the relative proportion of the bounds compared to max possible size. Max size
// spec takes the aspect ratio of the bounds into account, so both width and height
// scale by the same factor.
- addPipExclusionBoundsChangeCallback((bounds) -> {
- updateBoundsScale();
- });
+ addPipExclusionBoundsChangeCallback((bounds) -> updateBoundsScale());
}
/** Reloads the resources. */
@@ -341,11 +342,14 @@ public class PipBoundsState {
/** Set the last {@link ComponentName} to enter PIP mode. */
public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) {
final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName);
+ if (!changed) return;
+ clearReentryState();
+ setHasUserResizedPip(false);
+ setHasUserMovedPip(false);
+ final ComponentName oldComponentName = mLastPipComponentName;
mLastPipComponentName = lastPipComponentName;
- if (changed) {
- clearReentryState();
- setHasUserResizedPip(false);
- setHasUserMovedPip(false);
+ for (OnPipComponentChangedListener listener : mOnPipComponentChangedListeners) {
+ listener.onPipComponentChanged(oldComponentName, mLastPipComponentName);
}
}
@@ -616,6 +620,21 @@ public class PipBoundsState {
}
}
+ /** Adds callback to listen on component change. */
+ public void addOnPipComponentChangedListener(@NonNull OnPipComponentChangedListener listener) {
+ if (!mOnPipComponentChangedListeners.contains(listener)) {
+ mOnPipComponentChangedListeners.add(listener);
+ }
+ }
+
+ /** Removes callback to listen on component change. */
+ public void removeOnPipComponentChangedListener(
+ @NonNull OnPipComponentChangedListener listener) {
+ if (mOnPipComponentChangedListeners.contains(listener)) {
+ mOnPipComponentChangedListeners.remove(listener);
+ }
+ }
+
public LauncherState getLauncherState() {
return mLauncherState;
}
@@ -695,7 +714,7 @@ public class PipBoundsState {
* Represents the state of pip to potentially restore upon reentry.
*/
@VisibleForTesting
- public static final class PipReentryState {
+ static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
private final float mSnapFraction;
@@ -722,6 +741,22 @@ public class PipBoundsState {
}
}
+ /**
+ * Listener interface for PiP component change, i.e. the app in pip mode changes
+ * TODO: Move this out of PipBoundsState once pip1 is deprecated.
+ */
+ public interface OnPipComponentChangedListener {
+ /**
+ * Callback when the component in pip mode changes.
+ * @param oldPipComponent previous component in pip mode,
+ * {@code null} if this is the very first time PiP appears.
+ * @param newPipComponent new component that enters pip mode.
+ */
+ void onPipComponentChanged(
+ @Nullable ComponentName oldPipComponent,
+ @NonNull ComponentName newPipComponent);
+ }
+
/** Dumps internal state. */
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
index 3f21e74a7d03..1128fb2259b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -19,18 +19,14 @@ package com.android.wm.shell.common.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.app.ActivityManager;
import android.window.DesktopExperienceFlags;
+import android.window.DesktopModeFlags;
import android.window.DisplayAreaInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import com.android.window.flags.Flags;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
-import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import java.util.Optional;
@@ -38,37 +34,38 @@ import java.util.Optional;
public class PipDesktopState {
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
+ private final Optional<DragToDesktopTransitionHandler> mDragToDesktopTransitionHandlerOptional;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
+ Optional<DragToDesktopTransitionHandler> dragToDesktopTransitionHandlerOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mPipDisplayLayoutState = pipDisplayLayoutState;
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
+ mDragToDesktopTransitionHandlerOptional = dragToDesktopTransitionHandlerOptional;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
}
/**
* Returns whether PiP in Desktop Windowing is enabled by checking the following:
- * - Desktop Windowing in PiP flag is enabled
- * - DesktopWallpaperActivityTokenProvider is injected
+ * - PiP in Desktop Windowing flag is enabled
* - DesktopUserRepositories is injected
+ * - DragToDesktopTransitionHandler is injected
*/
public boolean isDesktopWindowingPipEnabled() {
- return Flags.enableDesktopWindowingPip()
- && mDesktopWallpaperActivityTokenProviderOptional.isPresent()
- && mDesktopUserRepositoriesOptional.isPresent();
+ return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()
+ && mDesktopUserRepositoriesOptional.isPresent()
+ && mDragToDesktopTransitionHandlerOptional.isPresent();
}
- /** Returns whether PiP in Connected Displays is enabled by checking the flag. */
+ /**
+ * Returns whether PiP in Connected Displays is enabled by checking the following:
+ * - PiP in Connected Displays flag is enabled
+ * - PiP2 flag is enabled
+ */
public boolean isConnectedDisplaysPipEnabled() {
- return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue();
+ return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() && Flags.enablePip2();
}
/** Returns whether the display with the PiP task is in freeform windowing mode. */
@@ -89,43 +86,7 @@ public class PipDesktopState {
return false;
}
final int displayId = mPipDisplayLayoutState.getDisplayId();
- return getDesktopRepository().isAnyDeskActive(displayId)
- || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
- || isDisplayInFreeform();
- }
-
- /** Returns whether {@param pipTask} would be entering in a Desktop Mode session. */
- public boolean isPipEnteringInDesktopMode(ActivityManager.RunningTaskInfo pipTask) {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!isDesktopWindowingPipEnabled()) {
- return false;
- }
- final DesktopRepository desktopRepository = getDesktopRepository();
- return desktopRepository.isAnyDeskActive(pipTask.getDisplayId())
- || desktopRepository.isMinimizedPipPresentInDisplay(pipTask.getDisplayId());
- }
-
- /**
- * Invoked when an EXIT_PiP transition is detected in {@link PipTransition}.
- * Returns whether the PiP exiting should also trigger the active Desktop Mode session to exit.
- */
- public boolean shouldExitPipExitDesktopMode() {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!isDesktopWindowingPipEnabled()) {
- return false;
- }
- final int displayId = mPipDisplayLayoutState.getDisplayId();
- return !getDesktopRepository().isAnyDeskActive(displayId)
- && getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId);
- }
-
- /**
- * Returns a {@link WindowContainerTransaction} that reorders the {@link WindowContainerToken}
- * of the DesktopWallpaperActivity for the display with the given {@param displayId}.
- */
- public WindowContainerTransaction getWallpaperActivityTokenWct(int displayId) {
- return new WindowContainerTransaction().reorder(
- getDesktopWallpaperActivityTokenProvider().getToken(displayId), /* onTop= */ false);
+ return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId);
}
/**
@@ -150,11 +111,12 @@ public class PipDesktopState {
return WINDOWING_MODE_UNDEFINED;
}
- private DesktopRepository getDesktopRepository() {
- return mDesktopUserRepositoriesOptional.get().getCurrent();
- }
-
- private DesktopWallpaperActivityTokenProvider getDesktopWallpaperActivityTokenProvider() {
- return mDesktopWallpaperActivityTokenProviderOptional.get();
+ /** Returns whether there is a drag-to-desktop transition in progress. */
+ public boolean isDragToDesktopInProgress() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ return mDragToDesktopTransitionHandlerOptional.get().getInProgress();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 4cbb78f2dae2..d36201a4ac9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -52,24 +52,15 @@ public class PipDoubleTapHelper {
public static final int SIZE_SPEC_MAX = 1;
public static final int SIZE_SPEC_CUSTOM = 2;
- /**
- * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
- *
- * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and
- * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between
- * the latter two sizes is determined based on the current state of the pip screen.</p>
- *
- * @param mPipBoundsState current state of the pip screen
- */
@PipSizeSpec
- private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) {
+ private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState pipBoundsState) {
// determine the average pip screen width
- int averageWidth = (mPipBoundsState.getMaxSize().x
- + mPipBoundsState.getMinSize().x) / 2;
+ int averageWidth = (pipBoundsState.getMaxSize().x
+ + pipBoundsState.getMinSize().x) / 2;
// If pip screen width is above average, DEFAULT is the size spec we need to
// toggle to. Otherwise, we choose MAX.
- return (mPipBoundsState.getBounds().width() > averageWidth)
+ return (pipBoundsState.getBounds().width() > averageWidth)
? SIZE_SPEC_DEFAULT
: SIZE_SPEC_MAX;
}
@@ -77,35 +68,33 @@ public class PipDoubleTapHelper {
/**
* Determines the {@link PipSizeSpec} to toggle to on double tap.
*
- * @param mPipBoundsState current state of the pip screen
+ * @param pipBoundsState current state of the pip bounds
* @param userResizeBounds latest user resized bounds (by pinching in/out)
- * @return pip screen size to switch to
*/
@PipSizeSpec
- public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ public static int nextSizeSpec(@NonNull PipBoundsState pipBoundsState,
@NonNull Rect userResizeBounds) {
- // is pip screen at its maximum
- boolean isScreenMax = mPipBoundsState.getBounds().width()
- == mPipBoundsState.getMaxSize().x;
-
- // is pip screen at its normal default size
- boolean isScreenDefault = (mPipBoundsState.getBounds().width()
- == mPipBoundsState.getNormalBounds().width())
- && (mPipBoundsState.getBounds().height()
- == mPipBoundsState.getNormalBounds().height());
+ boolean isScreenMax = pipBoundsState.getBounds().width() == pipBoundsState.getMaxSize().x
+ && pipBoundsState.getBounds().height() == pipBoundsState.getMaxSize().y;
+ boolean isScreenDefault = (pipBoundsState.getBounds().width()
+ == pipBoundsState.getNormalBounds().width())
+ && (pipBoundsState.getBounds().height()
+ == pipBoundsState.getNormalBounds().height());
// edge case 1
// if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet
// or if user has resized exactly to DEFAULT, then we just want to maximize
if (isScreenDefault
- && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) {
+ && userResizeBounds.width() == pipBoundsState.getNormalBounds().width()
+ && userResizeBounds.height() == pipBoundsState.getNormalBounds().height()) {
return SIZE_SPEC_MAX;
}
// edge case 2
- // if user has maximized, then we want to toggle to DEFAULT
+ // if user has resized to max, then we want to toggle to DEFAULT
if (isScreenMax
- && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) {
+ && userResizeBounds.width() == pipBoundsState.getMaxSize().x
+ && userResizeBounds.height() == pipBoundsState.getMaxSize().y) {
return SIZE_SPEC_DEFAULT;
}
@@ -113,9 +102,6 @@ public class PipDoubleTapHelper {
if (isScreenDefault || isScreenMax) {
return SIZE_SPEC_CUSTOM;
}
-
- // if we are currently in user resized CUSTOM size state
- // then we toggle either to MAX or DEFAULT depending on the current pip screen state
- return getMaxOrDefaultPipSizeSpec(mPipBoundsState);
+ return getMaxOrDefaultPipSizeSpec(pipBoundsState);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 5b2dd97a338f..bc0a8573f977 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -39,6 +39,7 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.ArrayList;
+import java.util.stream.IntStream;
/**
* Calculates the snap targets and the snap position given a position and a velocity. All positions
@@ -354,10 +355,20 @@ public class DividerSnapAlgorithm {
float ratio = areOffscreenRatiosSupported()
? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO
: SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+
+ // The intended size of the smaller app, in pixels
int size = (int) (ratio * (end - start)) - mDividerSize / 2;
- int leftTopPosition = start + pinnedTaskbarShiftStart + size;
- int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize;
+ // If there are insets that interfere with the smaller app (visually or blocking touch
+ // targets), make the smaller app bigger by that amount to compensate. This applies to
+ // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar (blocks
+ // touch targets at top).
+ int extraSpace = IntStream.of(
+ getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd
+ ).max().getAsInt();
+
+ int leftTopPosition = start + extraSpace + size;
+ int rightBottomPosition = end - extraSpace - size - mDividerSize;
addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 720e8e53b218..fec1f56c76bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -662,14 +662,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return;
}
- // Check to see if insets changed in such a way that the divider algorithm needs to be
- // recalculated.
+ // Check to see if insets changed in such a way that the divider needs to be animated to
+ // a new position. (We only do this when switching to pinned taskbar mode and back).
Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState);
if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
mPinnedTaskbarInsets = pinnedTaskbarInsets;
// Refresh the DividerSnapAlgorithm.
updateLayouts();
- // If the divider is no longer placed on a snap point, animate it to the nearest one.
+ // If the divider is no longer placed on a snap point, animate it to the nearest one
DividerSnapAlgorithm.SnapTarget snapTarget =
findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
if (snapTarget.position != mDividerPosition) {
@@ -683,18 +683,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
/**
- * Calculates the insets that might trigger a divider algorithm recalculation. Currently, only
- * pinned Taskbar does this, and only when the IME is not showing.
+ * Calculates the insets that might trigger a divider algorithm recalculation.
*/
private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) {
- if (insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
- return Insets.NONE;
- }
-
- // If IME is not showing...
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
- // and Taskbar is pinned...
+ // If Taskbar is pinned...
if (source.getType() == WindowInsets.Type.navigationBars()
&& source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
// Return Insets representing the pinned taskbar state.
@@ -1472,11 +1466,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// Freeze the configuration size with offset to prevent app get a configuration
// changed or relaunch. This is required to make sure client apps will calculate
// insets properly after layout shifted.
- if (mTargetYOffset == 0) {
- mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
- } else {
- mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
- }
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
}
// Make {@link DividerView} non-interactive while IME showing in split mode. Listen to
@@ -1543,11 +1533,28 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
+ /**
+ * When IME is triggered on the bottom app in split screen, we want to translate the bottom
+ * app up by a certain amount so that it's not covered too much by the IME. But there's also
+ * an upper limit to the amount we want to translate (since we still need some of the top
+ * app to be visible too). So this function essentially says "try to translate the bottom
+ * app up, but stop before you make the top app too small."
+ */
private int getTargetYOffset() {
- final int desireOffset = Math.abs(mEndImeTop - mStartImeTop);
- // Make sure to keep at least 30% visible for the top split.
- final int maxOffset = (int) (getTopLeftBounds().height() * ADJUSTED_SPLIT_FRACTION_MAX);
- return -Math.min(desireOffset, maxOffset);
+ // We want to translate up the bottom app by this amount.
+ final int desiredOffset = Math.abs(mEndImeTop - mStartImeTop);
+
+ // But we also want to keep this much of the top app visible.
+ final float amountOfTopAppToKeepVisible =
+ getTopLeftBounds().height() * (1 - ADJUSTED_SPLIT_FRACTION_MAX);
+
+ // So the current onscreen size of the top app, minus the minimum size, is the max
+ // translation we will allow.
+ final float currentOnScreenSizeOfTopApp = getTopLeftBounds().bottom;
+ final int maxOffset =
+ (int) Math.max(currentOnScreenSizeOfTopApp - amountOfTopAppToKeepVisible, 0);
+
+ return -Math.min(desiredOffset, maxOffset);
}
@SplitPosition
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 9b11e4ab16fa..4413c8715c0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -18,6 +18,8 @@ package com.android.wm.shell.compatui;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static com.android.wm.shell.compatui.impl.CompatUIRequestsKt.DISPLAY_COMPAT_SHOW_RESTART_DIALOG;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
@@ -53,7 +55,9 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.api.CompatUIEvent;
import com.android.wm.shell.compatui.api.CompatUIHandler;
import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.api.CompatUIRequest;
import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
+import com.android.wm.shell.compatui.impl.CompatUIRequests;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.KeyguardChangeListener;
@@ -245,6 +249,21 @@ public class CompatUIController implements OnDisplaysChangedListener,
mCallback = callback;
}
+ @Override
+ public void sendCompatUIRequest(CompatUIRequest compatUIRequest) {
+ switch(compatUIRequest.getRequestId()) {
+ case DISPLAY_COMPAT_SHOW_RESTART_DIALOG:
+ handleDisplayCompatShowRestartDialog(compatUIRequest.asType());
+ break;
+ default:
+ }
+ }
+
+ private void handleDisplayCompatShowRestartDialog(
+ CompatUIRequests.DisplayCompatShowRestartDialog request) {
+ onRestartButtonClicked(new Pair<>(request.getTaskInfo(), request.getTaskListener()));
+ }
+
/**
* Called when the Task info changed. Creates and updates the compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
@@ -254,13 +273,17 @@ public class CompatUIController implements OnDisplaysChangedListener,
public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
- if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
+ final boolean isInDisplayCompatMode =
+ taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove();
+ if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()
+ && !isInDisplayCompatMode) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
mIsInDesktopMode = isInDesktopMode(taskInfo);
// We close all the Compat UI educations in case TaskInfo has no configuration or
// TaskListener or in desktop mode.
- if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) {
+ if (taskInfo.configuration == null || taskListener == null
+ || (mIsInDesktopMode && !isInDisplayCompatMode)) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
return;
@@ -552,8 +575,11 @@ public class CompatUIController implements OnDisplaysChangedListener,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
RestartDialogWindowManager layout =
mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
+ final boolean isInNonDisplayCompatDesktopMode = mIsInDesktopMode
+ && !taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove();
if (layout != null) {
- if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
+ if (layout.needsToBeRecreated(taskInfo, taskListener)
+ || isInNonDisplayCompatDesktopMode) {
mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
layout.release();
} else {
@@ -568,8 +594,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
}
- if (mIsInDesktopMode) {
- // Return if in desktop mode.
+ if (isInNonDisplayCompatDesktopMode) {
+ // No restart dialog can be shown in desktop mode unless the task is in display compat
+ // mode.
return;
}
// Create a new UI layout.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
index 817e554b550e..f71f8099f29f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
@@ -28,6 +28,11 @@ interface CompatUIHandler {
fun onCompatInfoChanged(compatUIInfo: CompatUIInfo)
/**
+ * Invoked when another component in Shell requests a CompatUI state change.
+ */
+ fun sendCompatUIRequest(compatUIRequest: CompatUIRequest)
+
+ /**
* Optional reference to the object responsible to send {@link CompatUIEvent}
*/
fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt
new file mode 100644
index 000000000000..069fd9b062a6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compatui.api
+
+/**
+ * Abstraction for all the possible Compat UI Component requests.
+ */
+interface CompatUIRequest {
+ /**
+ * Unique request identifier
+ */
+ val requestId: Int
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T : CompatUIRequest> asType(): T? = this as? T
+
+ fun <T : CompatUIRequest> asType(clazz: Class<T>): T? {
+ return if (clazz.isInstance(this)) clazz.cast(this) else null
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
new file mode 100644
index 000000000000..da4fc99491dc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compatui.impl
+
+import android.app.TaskInfo
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.compatui.api.CompatUIRequest
+
+internal const val DISPLAY_COMPAT_SHOW_RESTART_DIALOG = 0
+
+/**
+ * All the {@link CompatUIRequest} the Compat UI Framework can handle
+ */
+sealed class CompatUIRequests(override val requestId: Int) : CompatUIRequest {
+ /** Sent when the restart handle menu is clicked, and a restart dialog is requested. */
+ data class DisplayCompatShowRestartDialog(val taskInfo: TaskInfo,
+ val taskListener: ShellTaskOrganizer.TaskListener) :
+ CompatUIRequests(DISPLAY_COMPAT_SHOW_RESTART_DIALOG)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index 02db85a4f99d..7dcb16c10097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -23,6 +23,7 @@ import com.android.wm.shell.compatui.api.CompatUIEvent
import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.compatui.api.CompatUIInfo
import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUIRequest
import com.android.wm.shell.compatui.api.CompatUIState
import java.util.function.Consumer
@@ -102,4 +103,6 @@ class DefaultCompatUIHandler(
override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) {
this.compatUIEventSender = compatUIEventSender
}
+
+ override fun sendCompatUIRequest(compatUIRequest: CompatUIRequest) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS
new file mode 100644
index 000000000000..007528e2e054
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module crash handling owners
+uysalorhan@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt
new file mode 100644
index 000000000000..2e34d043cbe1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.crashhandling
+
+import android.app.WindowConfiguration
+import android.content.Context
+import android.view.Display.DEFAULT_DISPLAY
+import android.window.DesktopExperienceFlags
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.HomeIntentProvider
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+
+/** [ShellCrashHandler] for shell to use when it's being initialized. Currently it only restores
+ * the home task to top.
+ **/
+class ShellCrashHandler(
+ private val context: Context,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val homeIntentProvider: HomeIntentProvider,
+ shellInit: ShellInit,
+) {
+ init {
+ shellInit.addInitCallback(::onInit, this)
+ }
+
+ private fun onInit() {
+ handleCrashIfNeeded()
+ }
+
+ private fun handleCrashIfNeeded() {
+ // For now only handle crashes when desktop mode is enabled on the device.
+ if (DesktopModeStatus.canEnterDesktopMode(context) &&
+ !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ var freeformTaskExists = false
+ // If there are running tasks at init, WMShell has crashed but WMCore is still alive.
+ for (task in shellTaskOrganizer.getRunningTasks()) {
+ if (task.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
+ freeformTaskExists = true
+ }
+
+ if (freeformTaskExists) {
+ shellTaskOrganizer.applyTransaction(
+ addLaunchHomePendingIntent(WindowContainerTransaction(), DEFAULT_DISPLAY)
+ )
+ break
+ }
+ }
+ }
+ }
+
+ private fun addLaunchHomePendingIntent(
+ wct: WindowContainerTransaction, displayId: Int
+ ): WindowContainerTransaction {
+ // TODO: b/400462917 - Check that crashes are also handled correctly on HSUM devices. We
+ // might need to pass the [userId] here to launch the correct home.
+ homeIntentProvider.addLaunchHomePendingIntent(wct, displayId)
+ return wct
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 0e6481b1c0ac..f62fd819319e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -95,6 +95,7 @@ import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.common.DefaultHomePackageSupplier;
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -260,8 +261,14 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(Context context) {
- return new DesktopModeCompatPolicy(context);
+ static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(
+ Context context,
+ ShellInit shellInit,
+ @ShellMainThread Handler mainHandler) {
+ final DesktopModeCompatPolicy policy = new DesktopModeCompatPolicy(context);
+ policy.setDefaultHomePackageSupplier(new DefaultHomePackageSupplier(
+ context, shellInit, mainHandler));
+ return policy;
}
@WMSingleton
@@ -1052,23 +1059,8 @@ public abstract class WMShellBaseModule {
});
}
- @WMSingleton
- @Provides
- static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() {
- return new DesktopWallpaperActivityTokenProvider();
- }
-
- @WMSingleton
- @Provides
- static Optional<DesktopWallpaperActivityTokenProvider>
- provideOptionalDesktopWallpaperActivityTokenProvider(
- Context context,
- DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider) {
- if (DesktopModeStatus.canEnterDesktopMode(context)) {
- return Optional.of(desktopWallpaperActivityTokenProvider);
- }
- return Optional.empty();
- }
+ @BindsOptionalOf
+ abstract DesktopWallpaperActivityTokenProvider optionalDesktopWallpaperActivityTokenProvider();
//
// App zoom out (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 79fbd32dafe6..67a4d6cf89bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -36,6 +36,7 @@ import android.os.Handler;
import android.os.UserManager;
import android.view.Choreographer;
import android.view.IWindowManager;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.DesktopModeFlags;
@@ -68,6 +69,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.HomeIntentProvider;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface;
@@ -79,6 +81,7 @@ import com.android.wm.shell.common.UserProfileContexts;
import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
+import com.android.wm.shell.crashhandling.ShellCrashHandler;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -93,7 +96,9 @@ import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
+import com.android.wm.shell.desktopmode.DesktopPipTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -167,6 +172,7 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
+import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper;
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.common.viewhost.PooledWindowDecorViewHostSupplier;
@@ -448,7 +454,8 @@ public abstract class WMShellModule {
Optional<DesktopImmersiveController> desktopImmersiveController,
WindowDecorViewModel windowDecorViewModel,
Optional<TaskChangeListener> taskChangeListener,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ Optional<DesksTransitionObserver> desksTransitionObserver) {
return new FreeformTaskTransitionObserver(
context,
shellInit,
@@ -456,7 +463,8 @@ public abstract class WMShellModule {
desktopImmersiveController,
windowDecorViewModel,
taskChangeListener,
- focusTransitionObserver);
+ focusTransitionObserver,
+ desksTransitionObserver);
}
@WMSingleton
@@ -722,9 +730,11 @@ public abstract class WMShellModule {
static DesksOrganizer provideDesksOrganizer(
@NonNull ShellInit shellInit,
@NonNull ShellCommandHandler shellCommandHandler,
- @NonNull ShellTaskOrganizer shellTaskOrganizer
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull LaunchAdjacentController launchAdjacentController
) {
- return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer);
+ return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer,
+ launchAdjacentController);
}
@WMSingleton
@@ -750,6 +760,7 @@ public abstract class WMShellModule {
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopUserRepositories desktopUserRepositories,
+ DesktopRepositoryInitializer desktopRepositoryInitializer,
Optional<DesktopImmersiveController> desktopImmersiveController,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
@@ -769,10 +780,13 @@ public abstract class WMShellModule {
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
- DesksTransitionObserver desksTransitionObserver,
+ Optional<DesksTransitionObserver> desksTransitionObserver,
+ Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DragToDisplayTransitionHandler dragToDisplayTransitionHandler) {
+ DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
+ DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler,
+ HomeIntentProvider homeIntentProvider) {
return new DesktopTasksController(
context,
shellInit,
@@ -794,6 +808,7 @@ public abstract class WMShellModule {
dragToDesktopTransitionHandler,
desktopImmersiveController.get(),
desktopUserRepositories,
+ desktopRepositoryInitializer,
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
@@ -809,10 +824,13 @@ public abstract class WMShellModule {
bubbleController,
overviewToDesktopTransitionObserver,
desksOrganizer,
- desksTransitionObserver,
+ desksTransitionObserver.get(),
+ desktopPipTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy,
- dragToDisplayTransitionHandler);
+ dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler,
+ homeIntentProvider);
}
@WMSingleton
@@ -869,12 +887,12 @@ public abstract class WMShellModule {
Transitions transitions,
@DynamicOverride DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer shellTaskOrganizer,
+ DesksOrganizer desksOrganizer,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler) {
int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
if (!DesktopModeStatus.canEnterDesktopMode(context)
- || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isTrue()
- || maxTaskLimit <= 0) {
+ || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isTrue()) {
return Optional.empty();
}
return Optional.of(
@@ -882,7 +900,8 @@ public abstract class WMShellModule {
transitions,
desktopUserRepositories,
shellTaskOrganizer,
- maxTaskLimit,
+ desksOrganizer,
+ maxTaskLimit <= 0 ? null : maxTaskLimit,
interactionJankMonitor,
context,
handler));
@@ -938,12 +957,24 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() {
+ return new DesktopWallpaperActivityTokenProvider();
+ }
+
+ @WMSingleton
+ @Provides
static DragToDisplayTransitionHandler provideDragToDisplayTransitionHandler() {
return new DragToDisplayTransitionHandler();
}
@WMSingleton
@Provides
+ static DesktopModeMoveToDisplayTransitionHandler provideMoveToDisplayTransitionHandler() {
+ return new DesktopModeMoveToDisplayTransitionHandler(new SurfaceControl.Transaction());
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler(
Context context,
Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel,
@@ -996,6 +1027,7 @@ public abstract class WMShellModule {
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
AppToWebEducationController appToWebEducationController,
+ AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
FocusTransitionObserver focusTransitionObserver,
@@ -1019,10 +1051,10 @@ public abstract class WMShellModule {
rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper,
desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
- windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
- focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
- taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy,
- desktopTilingDecorViewModel,
+ appHandleAndHeaderVisibilityHelper, windowDecorCaptionHandleRepository,
+ activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger,
+ desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler,
+ desktopModeCompatPolicy, desktopTilingDecorViewModel,
multiDisplayDragMoveIndicatorController));
}
@@ -1050,6 +1082,16 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static AppHandleAndHeaderVisibilityHelper provideAppHandleAndHeaderVisibilityHelper(
+ @NonNull Context context,
+ @NonNull DisplayController displayController,
+ @NonNull DesktopModeCompatPolicy desktopModeCompatPolicy) {
+ return new AppHandleAndHeaderVisibilityHelper(context, displayController,
+ desktopModeCompatPolicy);
+ }
+
+ @WMSingleton
+ @Provides
static WindowDecorTaskResourceLoader provideWindowDecorTaskResourceLoader(
@NonNull Context context, @NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -1186,9 +1228,9 @@ public abstract class WMShellModule {
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
+ Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
- @NonNull DesksTransitionObserver desksTransitionObserver,
ShellInit shellInit) {
return desktopUserRepositories.flatMap(
repository ->
@@ -1199,19 +1241,37 @@ public abstract class WMShellModule {
transitions,
shellTaskOrganizer,
desktopMixedTransitionHandler.get(),
+ desktopPipTransitionObserver,
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
- desksTransitionObserver,
shellInit)));
}
@WMSingleton
@Provides
- static DesksTransitionObserver provideDesksTransitionObserver(
- @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories,
+ static Optional<DesksTransitionObserver> provideDesksTransitionObserver(
+ Context context,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
@NonNull DesksOrganizer desksOrganizer
) {
- return new DesksTransitionObserver(desktopUserRepositories, desksOrganizer);
+ if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
+ return Optional.of(
+ new DesksTransitionObserver(desktopUserRepositories, desksOrganizer));
+ }
+ return Optional.empty();
+ }
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopPipTransitionObserver> provideDesktopPipTransitionObserver(
+ Context context
+ ) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
+ return Optional.of(
+ new DesktopPipTransitionObserver());
+ }
+ return Optional.empty();
}
@WMSingleton
@@ -1273,10 +1333,12 @@ public abstract class WMShellModule {
static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
Context context,
ShellInit shellInit,
+ @ShellMainThread CoroutineScope mainScope,
DisplayController displayController,
Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
- Optional<DesktopDisplayModeController> desktopDisplayModeController
+ Optional<DesktopDisplayModeController> desktopDisplayModeController,
+ DesktopRepositoryInitializer desktopRepositoryInitializer
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
@@ -1285,7 +1347,9 @@ public abstract class WMShellModule {
new DesktopDisplayEventHandler(
context,
shellInit,
+ mainScope,
displayController,
+ desktopRepositoryInitializer,
desktopUserRepositories.get(),
desktopTasksController.get(),
desktopDisplayModeController.get()));
@@ -1426,7 +1490,10 @@ public abstract class WMShellModule {
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
IWindowManager windowManager,
ShellTaskOrganizer shellTaskOrganizer,
- DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider
+ DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+ InputManager inputManager,
+ DisplayController displayController,
+ @ShellMainThread Handler mainHandler
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
@@ -1438,7 +1505,10 @@ public abstract class WMShellModule {
rootTaskDisplayAreaOrganizer,
windowManager,
shellTaskOrganizer,
- desktopWallpaperActivityTokenProvider));
+ desktopWallpaperActivityTokenProvider,
+ inputManager,
+ displayController,
+ mainHandler));
}
//
@@ -1520,7 +1590,8 @@ public abstract class WMShellModule {
Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler,
Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler,
- Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler) {
+ Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler,
+ ShellCrashHandler shellCrashHandler) {
return new Object();
}
@@ -1540,4 +1611,20 @@ public abstract class WMShellModule {
return new UserProfileContexts(context, shellController, shellInit);
}
+ @WMSingleton
+ @Provides
+ static ShellCrashHandler provideShellCrashHandler(
+ Context context,
+ ShellTaskOrganizer shellTaskOrganizer,
+ HomeIntentProvider homeIntentProvider,
+ ShellInit shellInit) {
+ return new ShellCrashHandler(context, shellTaskOrganizer, homeIntentProvider, shellInit);
+ }
+
+ @WMSingleton
+ @Provides
+ static HomeIntentProvider provideHomeIntentProvider(Context context) {
+ return new HomeIntentProvider(context);
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index f8b18f29c797..6f0919e1d045 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -42,9 +43,10 @@ import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTaskListener;
@@ -59,6 +61,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
@@ -87,12 +90,13 @@ public abstract class Pip2Module {
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
Optional<SplitScreenController> splitScreenControllerOptional,
- PipDesktopState pipDesktopState) {
+ PipDesktopState pipDesktopState,
+ PipInteractionHandler pipInteractionHandler) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
pipUiStateChangeController, displayController, splitScreenControllerOptional,
- pipDesktopState);
+ pipDesktopState, pipInteractionHandler);
}
@WMSingleton
@@ -209,8 +213,9 @@ public abstract class Pip2Module {
@WMSingleton
@Provides
- static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) {
- return new PipTransitionState(handler);
+ static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler,
+ PipDesktopState pipDesktopState) {
+ return new PipTransitionState(handler, pipDesktopState);
}
@WMSingleton
@@ -238,11 +243,23 @@ public abstract class Pip2Module {
static PipDesktopState providePipDesktopState(
PipDisplayLayoutState pipDisplayLayoutState,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
+ Optional<DragToDesktopTransitionHandler> dragToDesktopTransitionHandlerOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
return new PipDesktopState(pipDisplayLayoutState, desktopUserRepositoriesOptional,
- desktopWallpaperActivityTokenProviderOptional, rootTaskDisplayAreaOrganizer);
+ dragToDesktopTransitionHandlerOptional, rootTaskDisplayAreaOrganizer);
+ }
+
+ @BindsOptionalOf
+ abstract DragToDesktopTransitionHandler optionalDragToDesktopTransitionHandler();
+
+ @WMSingleton
+ @Provides
+ static PipInteractionHandler providePipInteractionHandler(
+ Context context,
+ @ShellMainThread Handler mainHandler
+ ) {
+ return new PipInteractionHandler(context, mainHandler,
+ InteractionJankMonitor.getInstance());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index afc48acad4f5..683b74392fa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -23,15 +23,21 @@ import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
/** Handles display events in desktop mode */
class DesktopDisplayEventHandler(
private val context: Context,
shellInit: ShellInit,
+ private val mainScope: CoroutineScope,
private val displayController: DisplayController,
+ private val desktopRepositoryInitializer: DesktopRepositoryInitializer,
private val desktopUserRepositories: DesktopUserRepositories,
private val desktopTasksController: DesktopTasksController,
private val desktopDisplayModeController: DesktopDisplayModeController,
@@ -61,15 +67,19 @@ class DesktopDisplayEventHandler(
logV("Display #$displayId does not support desks")
return
}
- logV("Creating new desk in new display#$displayId")
- // TODO: b/362720497 - when SystemUI crashes with a freeform task open for any reason, the
- // task is recreated and received in [FreeformTaskListener] before this display callback
- // is invoked, which results in the repository trying to add the task to a desk before the
- // desk has been recreated here, which may result in a crash-loop if the repository is
- // checking that the desk exists before adding a task to it. See b/391984373.
- desktopTasksController.createDesk(displayId)
- // TODO: b/393978539 - consider activating the desk on creation when applicable, such as
- // for connected displays.
+
+ mainScope.launch {
+ desktopRepositoryInitializer.isInitialized.collect { initialized ->
+ if (!initialized) return@collect
+ if (desktopRepository.getNumberOfDesks(displayId) == 0) {
+ logV("Creating new desk in new display#$displayId")
+ // TODO: b/393978539 - consider activating the desk on creation when
+ // applicable, such as for connected displays.
+ desktopTasksController.createDesk(displayId)
+ }
+ cancel()
+ }
+ }
}
override fun onDisplayRemoved(displayId: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
index e89aafe267ed..6dcc0deb1da1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -22,6 +22,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.windowingModeToString
import android.content.Context
+import android.hardware.input.InputManager
+import android.os.Handler
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.view.Display.DEFAULT_DISPLAY
@@ -29,11 +31,15 @@ import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DesktopExperienceFlags
import android.window.WindowContainerTransaction
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
/** Controls the display windowing mode in desktop mode */
@@ -44,8 +50,27 @@ class DesktopDisplayModeController(
private val windowManager: IWindowManager,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+ private val inputManager: InputManager,
+ private val displayController: DisplayController,
+ @ShellMainThread private val mainHandler: Handler,
) {
+ private val onTabletModeChangedListener =
+ object : InputManager.OnTabletModeChangedListener {
+ override fun onTabletModeChanged(whenNanos: Long, inTabletMode: Boolean) {
+ refreshDisplayWindowingMode()
+ }
+ }
+
+ init {
+ if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
+ inputManager.registerOnTabletModeChangedListener(
+ onTabletModeChangedListener,
+ mainHandler,
+ )
+ }
+ }
+
fun refreshDisplayWindowingMode() {
if (!DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) return
@@ -89,25 +114,48 @@ class DesktopDisplayModeController(
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
}
- private fun getTargetWindowingModeForDefaultDisplay(): Int {
+ @VisibleForTesting
+ fun getTargetWindowingModeForDefaultDisplay(): Int {
if (isExtendedDisplayEnabled() && hasExternalDisplay()) {
return WINDOWING_MODE_FREEFORM
}
+ if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
+ if (isInClamshellMode()) {
+ return WINDOWING_MODE_FREEFORM
+ }
+ return WINDOWING_MODE_FULLSCREEN
+ }
+
+ // If form factor-based desktop first switch is disabled, use the default display windowing
+ // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC).
return windowManager.getWindowingMode(DEFAULT_DISPLAY)
}
- // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- private fun isExtendedDisplayEnabled() =
- 0 !=
+ private fun isExtendedDisplayEnabled(): Boolean {
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue) {
+ return rootTaskDisplayAreaOrganizer
+ .getDisplayIds()
+ .filter { it != DEFAULT_DISPLAY }
+ .any { displayId ->
+ displayController.getDisplay(displayId)?.let { display ->
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ } ?: false
+ }
+ }
+
+ return 0 !=
Settings.Global.getInt(
context.contentResolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
0,
)
+ }
private fun hasExternalDisplay() =
rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY }
+ private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF
+
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 1f7edb413908..4646662073e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -452,6 +452,11 @@ class DesktopMixedTransitionHandler(
private fun findTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? =
info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
+ private fun findLaunchChange(info: TransitionInfo): TransitionInfo.Change? =
+ info.changes.firstOrNull { change ->
+ change.mode == TRANSIT_OPEN && change.taskInfo != null && change.taskInfo!!.isFreeform
+ }
+
private fun findDesktopTaskLaunchChange(
info: TransitionInfo,
launchTaskId: Int?,
@@ -459,14 +464,18 @@ class DesktopMixedTransitionHandler(
return if (launchTaskId != null) {
// Launching a known task (probably from background or moving to front), so
// specifically look for it.
- findTaskChange(info, launchTaskId)
+ val launchChange = findTaskChange(info, launchTaskId)
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX.isTrue &&
+ launchChange == null
+ ) {
+ findLaunchChange(info)
+ } else {
+ launchChange
+ }
} else {
// Launching a new task, so the first opening freeform task.
- info.changes.firstOrNull { change ->
- change.mode == TRANSIT_OPEN &&
- change.taskInfo != null &&
- change.taskInfo!!.isFreeform
- }
+ findLaunchChange(info)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 5269318943d9..1ea545f3ab67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -56,7 +56,6 @@ class DesktopModeKeyGestureHandler(
override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean {
if (
- !isKeyGestureSupported(event.keyGestureType) ||
!desktopTasksController.isPresent ||
!desktopModeWindowDecorViewModel.isPresent
) {
@@ -136,19 +135,6 @@ class DesktopModeKeyGestureHandler(
}
}
- override fun isKeyGestureSupported(gestureType: Int): Boolean =
- when (gestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY ->
- enableMoveToNextDisplayShortcut()
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
- KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW ->
- DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue &&
- manageKeyGestures()
- else -> false
- }
-
// TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it
// will pick a wrong task when a user quickly perform other actions with keyboard shortcuts
// after moveToNextDisplay, and move this to FocusTransitionObserver class.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt
new file mode 100644
index 000000000000..91bd3c9b6c22
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.os.IBinder
+import android.view.Choreographer
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.transition.Transitions
+import kotlin.time.Duration.Companion.milliseconds
+
+/** Transition handler for moving a window to a different display. */
+class DesktopModeMoveToDisplayTransitionHandler(
+ private val animationTransaction: SurfaceControl.Transaction
+) : Transitions.TransitionHandler {
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? = null
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ val changes = info.changes.filter { it.startDisplayId != it.endDisplayId }
+ if (changes.isEmpty()) return false
+ for (change in changes) {
+ val endBounds = change.endAbsBounds
+ // The position should be relative to the parent. For example, in ActivityEmbedding, the
+ // leash surface for the embedded Activity is parented to the container.
+ val endPosition = change.endRelOffset
+ startTransaction
+ .setPosition(change.leash, endPosition.x.toFloat(), endPosition.y.toFloat())
+ .setWindowCrop(change.leash, endBounds.width(), endBounds.height())
+ }
+ startTransaction.apply()
+
+ val animator = AnimatorSet()
+ animator.playTogether(
+ changes.map {
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = ANIM_DURATION.inWholeMilliseconds
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ animationTransaction
+ .setAlpha(it.leash, animation.animatedValue as Float)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
+ .apply()
+ }
+ }
+ }
+ )
+ animator.addListener(
+ object : Animator.AnimatorListener {
+ override fun onAnimationStart(animation: Animator) = Unit
+
+ override fun onAnimationEnd(animation: Animator) {
+ finishTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ finishTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ }
+
+ override fun onAnimationRepeat(animation: Animator) = Unit
+ }
+ )
+ animator.start()
+ return true
+ }
+
+ private companion object {
+ val ANIM_DURATION = 100.milliseconds
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index c5ee3137e5ba..a8b0bafee724 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -18,10 +18,15 @@
package com.android.wm.shell.desktopmode
-import android.annotation.DimenRes
import android.app.ActivityManager.RunningTaskInfo
import android.app.TaskInfo
-import android.content.Context
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.pm.ActivityInfo.LAUNCH_MULTIPLE
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
import android.content.pm.ActivityInfo.isFixedOrientationLandscape
import android.content.pm.ActivityInfo.isFixedOrientationPortrait
@@ -30,7 +35,8 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Rect
import android.os.SystemProperties
import android.util.Size
-import com.android.wm.shell.R
+import android.window.DesktopModeFlags
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import kotlin.math.ceil
@@ -256,12 +262,57 @@ fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
return taskBounds == stableBounds
}
-/** Returns the app header height in desktop mode in pixels. */
-fun getAppHeaderHeight(context: Context): Int =
- context.resources.getDimensionPixelSize(getAppHeaderHeightId())
+/**
+ * Returns the task bounds a launching task should inherit from an existing running instance.
+ * Returns null if there are no bounds to inherit.
+ */
+fun getInheritedExistingTaskBounds(
+ taskRepository: DesktopRepository,
+ shellTaskOrganizer: ShellTaskOrganizer,
+ task: RunningTaskInfo,
+ deskId: Int,
+): Rect? {
+ if (!DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue) return null
+ val activeTask = taskRepository.getExpandedTasksIdsInDeskOrdered(deskId).firstOrNull()
+ if (activeTask == null) return null
+ val lastTask = shellTaskOrganizer.getRunningTaskInfo(activeTask)
+ val lastTaskTopActivity = lastTask?.topActivity
+ val currentTaskTopActivity = task.topActivity
+ val intentFlags = task.baseIntent.flags
+ val launchMode = task.topActivityInfo?.launchMode ?: LAUNCH_MULTIPLE
+ return when {
+ // No running task activity to inherit bounds from.
+ lastTaskTopActivity == null -> null
+ // No current top activity to set bounds for.
+ currentTaskTopActivity == null -> null
+ // Top task is not an instance of the launching activity, do not inherit its bounds.
+ lastTaskTopActivity.packageName != currentTaskTopActivity.packageName -> null
+ // Top task is an instance of launching activity. Activity will be launching in a new
+ // task with the existing task also being closed. Inherit existing task bounds to
+ // prevent new task jumping.
+ (isLaunchingNewTask(launchMode, intentFlags) && isClosingExitingInstance(intentFlags)) ->
+ lastTask.configuration.windowConfiguration.bounds
+ else -> null
+ }
+}
-/** Returns the resource id of the app header height in desktop mode. */
-@DimenRes fun getAppHeaderHeightId(): Int = R.dimen.desktop_mode_freeform_decor_caption_height
+/**
+ * Returns true if the launch mode or intent will result in a new task being created for the
+ * activity.
+ */
+private fun isLaunchingNewTask(launchMode: Int, intentFlags: Int) =
+ launchMode == LAUNCH_SINGLE_TASK ||
+ launchMode == LAUNCH_SINGLE_INSTANCE ||
+ launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK ||
+ (intentFlags and FLAG_ACTIVITY_NEW_TASK) != 0
+
+/**
+ * Returns true if the intent will result in an existing task instance being closed if a new one
+ * appears.
+ */
+private fun isClosingExitingInstance(intentFlags: Int) =
+ (intentFlags and FLAG_ACTIVITY_CLEAR_TASK) != 0 ||
+ (intentFlags and FLAG_ACTIVITY_MULTIPLE_TASK) == 0
/**
* Calculates the desired initial bounds for applications in desktop windowing. This is done as a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 56de48daf810..1938e76cad49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
@@ -32,6 +33,7 @@ import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
+import android.view.Display;
import android.view.SurfaceControl;
import android.window.DesktopModeFlags;
@@ -103,6 +105,10 @@ public class DesktopModeVisualIndicator {
return null;
}
}
+
+ private static boolean isDragToDesktopStartState(DragStartState startState) {
+ return startState == FROM_FULLSCREEN || startState == FROM_SPLIT;
+ }
}
private final VisualIndicatorViewContainer mVisualIndicatorViewContainer;
@@ -110,6 +116,7 @@ public class DesktopModeVisualIndicator {
private final Context mContext;
private final DisplayController mDisplayController;
private final ActivityManager.RunningTaskInfo mTaskInfo;
+ private final Display mDisplay;
private IndicatorType mCurrentType;
private final DragStartState mDragStartState;
@@ -125,7 +132,12 @@ public class DesktopModeVisualIndicator {
@Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider,
SnapEventHandler snapEventHandler) {
SurfaceControl.Builder builder = new SurfaceControl.Builder();
- taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder);
+ if (!DragStartState.isDragToDesktopStartState(dragStartState)
+ || !DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue()) {
+ // In the DragToDesktop transition we attach the indicator to the transition root once
+ // that is available - for all other cases attach the indicator here.
+ taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder);
+ }
mVisualIndicatorViewContainer = new VisualIndicatorViewContainer(
DesktopModeFlags.ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX.isTrue()
? desktopExecutor : mainExecutor,
@@ -136,9 +148,10 @@ public class DesktopModeVisualIndicator {
mCurrentType = NO_INDICATOR;
mDragStartState = dragStartState;
mSnapEventHandler = snapEventHandler;
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
mVisualIndicatorViewContainer.createView(
mContext,
- mDisplayController.getDisplay(mTaskInfo.displayId),
+ mDisplay,
mDisplayController.getDisplayLayout(mTaskInfo.displayId),
mTaskInfo,
taskSurface
@@ -159,6 +172,19 @@ public class DesktopModeVisualIndicator {
mVisualIndicatorViewContainer.releaseVisualIndicator();
}
+ /** Reparent the visual indicator to {@code newParent}. */
+ void reparentLeash(SurfaceControl.Transaction t, SurfaceControl newParent) {
+ mVisualIndicatorViewContainer.reparentLeash(t, newParent);
+ }
+
+ /** Start the fade-in animation. */
+ void fadeInIndicator() {
+ if (mCurrentType == NO_INDICATOR) return;
+ mVisualIndicatorViewContainer.fadeInIndicator(
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+ mTaskInfo.displayId);
+ }
+
/**
* Based on the coordinates of the current drag event, determine which indicator type we should
* display, including no visible indicator.
@@ -172,7 +198,7 @@ public class DesktopModeVisualIndicator {
if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR;
IndicatorType result;
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
- && !DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ && !DesktopModeStatus.isDesktopModeSupportedOnDisplay(mContext, mDisplay)) {
// If desktop is not available, default to "no indicator"
result = NO_INDICATOR;
} else {
@@ -187,8 +213,7 @@ public class DesktopModeVisualIndicator {
com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
// Because drags in freeform use task position for indicator calculation, we need to
// account for the possibility of the task going off the top of the screen by captionHeight
- final int captionHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
+ final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext);
final Region fullscreenRegion = calculateFullscreenRegion(layout, captionHeight);
final Region splitLeftRegion = calculateSplitLeftRegion(layout, transitionAreaWidth,
captionHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
new file mode 100644
index 000000000000..efd3866e1bc4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.IBinder
+import android.window.DesktopModeFlags
+import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+
+/**
+ * Observer of PiP in Desktop Mode transitions. At the moment, this is specifically tracking a PiP
+ * transition for a task that is entering PiP via the minimize button on the caption bar.
+ */
+class DesktopPipTransitionObserver {
+ private val pendingPipTransitions = mutableMapOf<IBinder, PendingPipTransition>()
+
+ /** Adds a pending PiP transition to be tracked. */
+ fun addPendingPipTransition(transition: PendingPipTransition) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
+ pendingPipTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
+ val pipTransition = pendingPipTransitions.remove(transition) ?: return
+
+ logD("Desktop PiP transition ready: %s", transition)
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ if (
+ taskInfo.taskId == pipTransition.taskId &&
+ taskInfo.windowingMode == WINDOWING_MODE_PINNED
+ ) {
+ logD("Desktop PiP transition was successful")
+ pipTransition.onSuccess()
+ return
+ }
+ }
+ logD("Change with PiP task not found in Desktop PiP transition; likely failed")
+ }
+
+ /**
+ * Data tracked for a pending PiP transition.
+ *
+ * @property token the PiP transition that is started.
+ * @property taskId task id of the task entering PiP.
+ * @property onSuccess callback to be invoked if the PiP transition is successful.
+ */
+ data class PendingPipTransition(val token: IBinder, val taskId: Int, val onSuccess: () -> Unit)
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private companion object {
+ private const val TAG = "DesktopPipTransitionObserver"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 73df9767ee50..6cb26b54e802 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -28,7 +28,6 @@ import androidx.core.util.forEach
import androidx.core.util.valueIterator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -69,10 +68,6 @@ class DesktopRepository(
* @property topTransparentFullscreenTaskId the task id of any current top transparent
* fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
* sent to back. (top is at index 0).
- * @property pipTaskId the task id of PiP task entered while in Desktop Mode.
- * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk
- * active. Only false when we are explicitly exiting Desktop Mode (via user action) while
- * there is an active PiP window.
*/
private data class Desk(
val deskId: Int,
@@ -85,9 +80,6 @@ class DesktopRepository(
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
- var pipTaskId: Int? = null,
- // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId].
- var pipShouldKeepDesktopActive: Boolean = true,
) {
fun deepCopy(): Desk =
Desk(
@@ -100,8 +92,6 @@ class DesktopRepository(
freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
- pipTaskId = pipTaskId,
- pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
)
// TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -114,8 +104,6 @@ class DesktopRepository(
freeformTasksInZOrder.clear()
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
- pipTaskId = null
- pipShouldKeepDesktopActive = true
}
}
@@ -135,9 +123,6 @@ class DesktopRepository(
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
- /* Callback for when a pending PiP transition has been aborted. */
- private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null
-
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -243,6 +228,10 @@ class DesktopRepository(
/** Returns the default desk in the given display. */
private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
+ /** Returns whether the given desk is active in its display. */
+ fun isDeskActive(deskId: Int): Boolean =
+ desktopData.getAllActiveDesks().any { desk -> desk.deskId == deskId }
+
/** Sets the given desk as the active one in the given display. */
fun setActiveDesk(displayId: Int, deskId: Int) {
logD("setActiveDesk for displayId=%d and deskId=%d", displayId, deskId)
@@ -343,7 +332,7 @@ class DesktopRepository(
val affectedDisplays = mutableSetOf<Int>()
desktopData
.desksSequence()
- .filter { desk -> desk.displayId != excludedDeskId }
+ .filter { desk -> desk.deskId != excludedDeskId }
.forEach { desk ->
val removed = removeActiveTaskFromDesk(desk.deskId, taskId, notifyListeners = false)
if (removed) {
@@ -493,7 +482,7 @@ class DesktopRepository(
fun getExpandedTasksOrdered(displayId: Int): List<Int> =
getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
- @VisibleForTesting
+ /** Returns all active non-minimized tasks for [deskId] ordered from top to bottom. */
fun getExpandedTasksIdsInDeskOrdered(deskId: Int): List<Int> =
getFreeformTasksIdsInDeskInZOrder(deskId).filter { !isMinimizedTask(it) }
@@ -615,87 +604,6 @@ class DesktopRepository(
}
/**
- * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
- val activeDesk =
- desktopData.getActiveDesk(displayId)
- ?: error("Expected active desk in display: $displayId")
- if (enterPip) {
- activeDesk.pipTaskId = taskId
- activeDesk.pipShouldKeepDesktopActive = true
- } else {
- activeDesk.pipTaskId =
- if (activeDesk.pipTaskId == taskId) null
- else {
- logW(
- "setTaskInPip: taskId=%d did not match saved taskId=%d",
- taskId,
- activeDesk.pipTaskId,
- )
- activeDesk.pipTaskId
- }
- }
- notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId))
- }
-
- /**
- * Returns whether there is a PiP that was entered/minimized from Desktop in this display's
- * active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean =
- desktopData.getActiveDesk(displayId)?.pipTaskId != null
-
- /**
- * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
- desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
-
- /**
- * Returns whether a desk should be active in this display due to active PiP.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun shouldDesktopBeActiveForPip(displayId: Int): Boolean =
- Flags.enableDesktopWindowingPip() &&
- isMinimizedPipPresentInDisplay(displayId) &&
- (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false)
-
- /**
- * Saves whether a PiP window should keep Desktop session active in this display.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) {
- desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive
- }
-
- /**
- * Saves callback to handle a pending PiP transition being aborted.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
- onPipAbortedCallback = callbackIfPipAborted
- }
-
- /**
- * Invokes callback to handle a pending PiP transition with the given task id being aborted.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun onPipAborted(displayId: Int, pipTaskId: Int) {
- onPipAbortedCallback?.invoke(displayId, pipTaskId)
- }
-
- /**
* Set whether the given task is the full-immersive task in this display's active desk.
*
* TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with
@@ -750,12 +658,15 @@ class DesktopRepository(
}
/**
- * Returns the top transparent fullscreen task id for a given display's active desk, or null.
+ * Returns the top transparent fullscreen task id for a given display, or null.
*
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun getTopTransparentFullscreenTaskId(displayId: Int): Int? =
- desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId
+ desktopData
+ .desksSequence(displayId)
+ .mapNotNull { it.topTransparentFullscreenTaskId }
+ .firstOrNull()
/**
* Clears the top transparent fullscreen task id info for a given display's active desk.
@@ -771,13 +682,10 @@ class DesktopRepository(
desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
}
- private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
- val visibleAndPipTasksCount =
- if (shouldDesktopBeActiveForPip(displayId)) visibleTasksCount + 1 else visibleTasksCount
+ @VisibleForTesting
+ public fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute {
- listener.onTasksVisibilityChanged(displayId, visibleAndPipTasksCount)
- }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
@@ -855,7 +763,6 @@ class DesktopRepository(
}
/** Minimizes the task in its desk. */
- @VisibleForTesting
fun minimizeTaskInDesk(displayId: Int, deskId: Int, taskId: Int) {
logD("MinimizeTaskInDesk: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId)
desktopData.getDesk(deskId)?.minimizedTasks?.add(taskId)
@@ -957,6 +864,7 @@ class DesktopRepository(
val wasActive = desktopData.getActiveDesk(desk.displayId)?.deskId == desk.deskId
val activeTasks = ArraySet(desk.activeTasks)
desktopData.remove(desk.deskId)
+ notifyVisibleTaskListeners(desk.displayId, getVisibleTaskCount(displayId = desk.displayId))
deskChangeListeners.forEach { (listener, executor) ->
executor.execute {
if (wasActive) {
@@ -969,6 +877,12 @@ class DesktopRepository(
listener.onDeskRemoved(displayId = desk.displayId, deskId = desk.deskId)
}
}
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue &&
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
+ ) {
+ removeDeskFromPersistentRepository(desk)
+ }
return activeTasks
}
@@ -1067,6 +981,24 @@ class DesktopRepository(
}
}
+ private fun removeDeskFromPersistentRepository(desk: Desk) {
+ mainCoroutineScope.launch {
+ try {
+ logD(
+ "updatePersistentRepositoryForRemovedDesk user=%d desk=%d",
+ userId,
+ desk.deskId,
+ )
+ persistentRepository.removeDesktop(userId = userId, desktopId = desk.deskId)
+ } catch (throwable: Throwable) {
+ logE(
+ "An exception occurred while updating the persistent repository \n%s",
+ throwable.stackTrace,
+ )
+ }
+ }
+ }
+
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopRepository")
@@ -1085,6 +1017,7 @@ class DesktopRepository(
}
.forEach { (displayId, activeDeskId, desks) ->
pw.println("${prefix}Display #$displayId:")
+ pw.println("${innerPrefix}numOfDesks=${desks.size}")
pw.println("${innerPrefix}activeDesk=$activeDeskId")
pw.println("${innerPrefix}desks:")
val desksPrefix = "$innerPrefix "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
index 70a648f57125..e04d14459284 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
@@ -53,24 +53,12 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
// Case 1: When the task change is from a task in the desktop repository which is now
// fullscreen,
// remove the task from the desktop repository since it is no longer a freeform task.
- if (!isFreeformTask(taskInfo)) {
- if (desktopRepository.isActiveTask(taskInfo.taskId)) {
- desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
- }
- } else { // Task change is a freeform task
- if (!desktopRepository.isActiveTask(taskInfo.taskId)) {
- // Case 2: When the task change is a freeform visible task, but the task is not
- // yet active in the desktop repository, adds task to desktop repository.
- desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
- } else {
- // Case 3: When the task change is a freeform task which already exists as an active
- // task in the desktop repository, updates the task state.
- desktopRepository.updateTask(
- taskInfo.displayId,
- taskInfo.taskId,
- taskInfo.isVisible,
- )
- }
+ if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
+ desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
+ } else if (isFreeformTask(taskInfo)) {
+ // If the task is already active in the repository, then moves task to the front,
+ // else adds the task.
+ desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
}
}
@@ -109,7 +97,7 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
desktopUserRepositories.getProfile(taskInfo.userId)
if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
logD("onTaskMovingToBack for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
- // TODO: b/367268953 - Connect this with DesktopRepository.
+ desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */ false)
}
override fun onTaskClosing(taskInfo: RunningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 8f7e52ea2108..50f5beb4166a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -22,6 +22,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.KeyguardManager
import android.app.PendingIntent
+import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -42,11 +43,13 @@ import android.os.IBinder
import android.os.SystemProperties
import android.os.UserHandle
import android.util.Slog
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
+import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
@@ -72,6 +75,7 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx
import com.android.internal.protolog.ProtoLog
import com.android.internal.util.LatencyTracker
import com.android.window.flags.Flags
@@ -83,6 +87,7 @@ import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.HomeIntentProvider
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent
import com.android.wm.shell.common.RemoteCallable
@@ -110,6 +115,9 @@ import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
+import com.android.wm.shell.desktopmode.multidesks.createDesk
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -189,6 +197,7 @@ class DesktopTasksController(
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
private val userRepositories: DesktopUserRepositories,
+ desktopRepositoryInitializer: DesktopRepositoryInitializer,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor,
@@ -205,9 +214,12 @@ class DesktopTasksController(
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
private val desksTransitionObserver: DesksTransitionObserver,
+ private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
+ private val moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler,
+ private val homeIntentProvider: HomeIntentProvider,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -233,6 +245,10 @@ class DesktopTasksController(
removeVisualIndicator()
}
+ override fun onTransitionInterrupted() {
+ removeVisualIndicator()
+ }
+
private fun removeVisualIndicator() {
visualIndicator?.fadeOutIndicator { releaseVisualIndicator() }
}
@@ -265,6 +281,19 @@ class DesktopTasksController(
}
userId = ActivityManager.getCurrentUser()
taskRepository = userRepositories.getProfile(userId)
+
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desktopRepositoryInitializer.deskRecreationFactory =
+ DeskRecreationFactory { deskUserId, destinationDisplayId, deskId ->
+ if (deskUserId != userId) {
+ // TODO: b/400984250 - add multi-user support for multi-desk restoration.
+ logW("Tried to recreated desk of another user.")
+ deskId
+ } else {
+ desksOrganizer.createDesk(destinationDisplayId)
+ }
+ }
+ }
}
private fun onInit() {
@@ -343,45 +372,28 @@ class DesktopTasksController(
fun isAnyDeskActive(displayId: Int): Boolean = taskRepository.isAnyDeskActive(displayId)
/**
- * Returns true if any of the following is true:
- * - Any freeform tasks are visible
- * - A transparent fullscreen task exists on top in Desktop Mode
- * - PiP on Desktop Windowing is enabled, there is an active PiP window and the desktop
- * wallpaper is visible.
+ * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on
+ * top in Desktop Mode.
*
* TODO: b/362720497 - consolidate with [isAnyDeskActive].
* - top-transparent-fullscreen case: should not be needed if we allow it to launch inside
* the desk in fullscreen instead of force-exiting desktop and having to trick this method
* into thinking it is in desktop mode when a task in this state exists.
- * - PIP case: a PIP presence should influence desk activation, so
- * [DesktopRepository#isAnyDeskActive] should be sufficient.
*/
fun isDesktopModeShowing(displayId: Int): Boolean {
val hasVisibleTasks = taskRepository.isAnyDeskActive(displayId)
val hasTopTransparentFullscreenTask =
taskRepository.getTopTransparentFullscreenTaskId(displayId) != null
- val hasMinimizedPip =
- Flags.enableDesktopWindowingPip() &&
- taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
- desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(displayId)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
) {
logV(
- "isDesktopModeShowing: hasVisibleTasks=%s hasTopTransparentFullscreenTask=%s hasMinimizedPip=%s",
+ "isDesktopModeShowing: hasVisibleTasks=%s hasTopTransparentFullscreenTask=%s",
hasVisibleTasks,
hasTopTransparentFullscreenTask,
- hasMinimizedPip,
- )
- return hasVisibleTasks || hasTopTransparentFullscreenTask || hasMinimizedPip
- } else if (Flags.enableDesktopWindowingPip()) {
- logV(
- "isDesktopModeShowing: hasVisibleTasks=%s hasMinimizedPip=%s",
- hasVisibleTasks,
- hasMinimizedPip,
)
- return hasVisibleTasks || hasMinimizedPip
+ return hasVisibleTasks || hasTopTransparentFullscreenTask
}
logV("isDesktopModeShowing: hasVisibleTasks=%s", hasVisibleTasks)
return hasVisibleTasks
@@ -453,6 +465,10 @@ class DesktopTasksController(
/** Creates a new desk in the given display. */
fun createDesk(displayId: Int) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ logW("createDesk attempt with invalid displayId", displayId)
+ return
+ }
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksOrganizer.createDesk(displayId) { deskId ->
taskRepository.addDesk(displayId = displayId, deskId = deskId)
@@ -537,8 +553,8 @@ class DesktopTasksController(
return false
}
logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
- val taskIdToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, taskId)
+ val deskId = getDefaultDeskId(task.displayId)
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -567,9 +583,7 @@ class DesktopTasksController(
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
+ runOnTransitStart?.invoke(transition)
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
return true
}
@@ -603,13 +617,7 @@ class DesktopTasksController(
reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
- val taskIdToMinimize =
- prepareMoveTaskToDeskAndActivate(
- wct = wct,
- displayId = displayId,
- deskId = deskId,
- task = task,
- )
+ val runOnTransitStart = addDeskActivationWithMovingTaskChanges(deskId, wct, task)
val transition: IBinder
if (remoteTransition != null) {
@@ -624,63 +632,14 @@ class DesktopTasksController(
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
+ runOnTransitStart?.invoke(transition)
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = displayId,
- deskId = deskId,
- enterTaskId = task.taskId,
- )
- )
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
taskRepository.setActiveDesk(displayId = displayId, deskId = deskId)
}
return true
}
- /**
- * Applies the necessary changes and operations to [wct] to move a task into a desk and
- * activating that desk. This includes showing pre-existing tasks of that desk behind the new
- * task (but minimizing one of them if needed) and showing Home and the desktop wallpaper.
- *
- * @return the id of the task that is being minimized, if any.
- */
- private fun prepareMoveTaskToDeskAndActivate(
- wct: WindowContainerTransaction,
- displayId: Int,
- deskId: Int,
- task: RunningTaskInfo,
- ): Int? {
- val taskIdToMinimize =
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- // Activate the desk first.
- prepareForDeskActivation(displayId, wct)
- desksOrganizer.activateDesk(wct, deskId)
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
- // TODO: b/362720497 - do non-running tasks need to be restarted with
- // |wct#startTask|?
- }
- taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
- doesAnyTaskRequireTaskbarRounding(displayId)
- )
- // TODO: b/362720497 - activating a desk with the intention to move a new task to
- // it means we may need to minimize something in the activating desk. Do so here
- // similar to how it's done in #bringDesktopAppsToFrontBeforeShowingNewTask
- // instead of returning null.
- null
- } else {
- // Bring other apps to front first.
- bringDesktopAppsToFrontBeforeShowingNewTask(displayId, wct, task.taskId)
- }
- addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
- return taskIdToMinimize
- }
-
private fun invokeCallbackToOverview(transition: IBinder, callback: IMoveToDesktopCallback?) {
// TODO: b/333524374 - Remove this later.
// This is a temporary implementation for adding CUJ end and
@@ -698,6 +657,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
taskSurface: SurfaceControl,
+ dragInterruptedCallback: Runnable,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
val jankConfigBuilder =
@@ -712,6 +672,8 @@ class DesktopTasksController(
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo,
dragToDesktopValueAnimator,
+ visualIndicator,
+ dragInterruptedCallback,
)
}
@@ -738,13 +700,7 @@ class DesktopTasksController(
moveHomeTask(context.displayId, wct)
}
}
- val taskIdToMinimize =
- prepareMoveTaskToDeskAndActivate(
- wct = wct,
- displayId = taskInfo.displayId,
- deskId = deskId,
- task = taskInfo,
- )
+ val runOnTransitStart = addDeskActivationWithMovingTaskChanges(deskId, wct, taskInfo)
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -757,20 +713,9 @@ class DesktopTasksController(
DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
)
if (transition != null) {
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
+ runOnTransitStart?.invoke(transition)
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = taskInfo.displayId,
- deskId = deskId,
- enterTaskId = taskInfo.taskId,
- )
- )
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
taskRepository.setActiveDesk(displayId = taskInfo.displayId, deskId = deskId)
}
} else {
@@ -818,7 +763,6 @@ class DesktopTasksController(
displayId = displayId,
forceExitDesktop = false,
)
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
val desktopExitRunnable =
performDesktopExitCleanUp(
wct = wct,
@@ -850,8 +794,31 @@ class DesktopTasksController(
fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
+ val taskId = taskInfo.taskId
+ val displayId = taskInfo.displayId
+ val deskId =
+ taskRepository.getDeskIdForTask(taskInfo.taskId)
+ ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ logW("minimizeTask: desk not found for task: ${taskInfo.taskId}")
+ return
+ } else {
+ getDefaultDeskId(taskInfo.displayId)
+ }
+ val isLastTask =
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ taskRepository.isOnlyVisibleNonClosingTaskInDesk(
+ taskId = taskId,
+ deskId = checkNotNull(deskId) { "Expected non-null deskId" },
+ displayId = displayId,
+ )
+ } else {
+ taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+ }
+ val isMinimizingToPip =
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
+ desktopPipTransitionObserver.isPresent &&
+ (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false)
- val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
// If task is going to PiP, start a PiP transition instead of a minimize transition
if (isMinimizingToPip) {
val requestInfo =
@@ -865,76 +832,60 @@ class DesktopTasksController(
)
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
- freeformTaskTransitionStarter.startPipTransition(wct)
- taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
- taskRepository.setOnPipAbortedCallback { displayId, taskId ->
- minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
- taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
- }
- return
- }
- minimizeTaskInner(taskInfo, minimizeReason)
- }
-
- private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
- val taskId = taskInfo.taskId
- val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
- if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}")
- return
- }
- val displayId = taskInfo.displayId
- val wct = WindowContainerTransaction()
-
- snapEventHandler.removeTaskIfTiled(displayId, taskId)
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
- val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
- val desktopExitRunnable =
- performDesktopExitCleanUp(
- wct = wct,
- deskId = deskId,
- displayId = displayId,
- willExitDesktop = willExitDesktop,
- )
- // Notify immersive handler as it might need to exit immersive state.
- val exitResult =
- desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.MINIMIZED,
- )
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.minimizeTask(
- wct = wct,
- deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- task = taskInfo,
+ desktopPipTransitionObserver.get().addPendingPipTransition(
+ DesktopPipTransitionObserver.PendingPipTransition(
+ token = freeformTaskTransitionStarter.startPipTransition(wct),
+ taskId = taskInfo.taskId,
+ onSuccess = {
+ onDesktopTaskEnteredPip(
+ taskId = taskId,
+ deskId = deskId,
+ displayId = taskInfo.displayId,
+ taskIsLastVisibleTaskBeforePip = isLastTask,
+ )
+ },
+ )
)
} else {
- wct.reorder(taskInfo.token, /* onTop= */ false)
- }
- val isLastTask =
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
+ val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = willExitDesktop,
+ )
+ // Notify immersive handler as it might need to exit immersive state.
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.MINIMIZED,
+ )
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- taskRepository.isOnlyVisibleNonClosingTaskInDesk(
- taskId = taskId,
+ desksOrganizer.minimizeTask(
+ wct = wct,
deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- displayId = displayId,
+ task = taskInfo,
)
} else {
- taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+ wct.reorder(taskInfo.token, /* onTop= */ false)
}
- val transition =
- freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
- desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(
- transition = transition,
- displayId = displayId,
- taskId = taskId,
- minimizeReason = minimizeReason,
- )
+ val transition =
+ freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
+ desktopTasksLimiter.ifPresent {
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = displayId,
+ taskId = taskId,
+ minimizeReason = minimizeReason,
+ )
+ }
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ desktopExitRunnable?.invoke(transition)
}
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- desktopExitRunnable?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
@@ -1003,11 +954,9 @@ class DesktopTasksController(
// handles case where we are moving to full screen without closing all DW tasks.
if (
!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)
- // This callback is already invoked by |addMoveToFullscreenChanges| when one of these
- // flags is enabled.
- &&
- !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
- !Flags.enableDesktopWindowingPip()
+ // This callback is already invoked by |addMoveToFullscreenChanges| when this flag is
+ // enabled.
+ && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
) {
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
@@ -1053,10 +1002,13 @@ class DesktopTasksController(
.apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
.toBundle(),
)
+ val deskId = taskRepository.getDeskIdForTask(taskId) ?: getDefaultDeskId(DEFAULT_DISPLAY)
startLaunchTransition(
TRANSIT_OPEN,
wct,
taskId,
+ deskId = deskId,
+ displayId = DEFAULT_DISPLAY,
remoteTransition = remoteTransition,
unminimizeReason = unminimizeReason,
)
@@ -1074,19 +1026,26 @@ class DesktopTasksController(
remoteTransition: RemoteTransition? = null,
unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN,
) {
- logV("moveTaskToFront taskId=%s", taskInfo.taskId)
+ val deskId =
+ taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getDefaultDeskId(taskInfo.displayId)
+ logV("moveTaskToFront taskId=%s deskId=%s", taskInfo.taskId, deskId)
// If a task is tiled, another task should be brought to foreground with it so let
// tiling controller handle the request.
if (snapEventHandler.moveTaskToFrontIfTiled(taskInfo)) {
return
}
val wct = WindowContainerTransaction()
- wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true)
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desksOrganizer.reorderTaskToFront(wct, deskId, taskInfo)
+ } else {
+ wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true)
+ }
startLaunchTransition(
transitionType = TRANSIT_TO_FRONT,
wct = wct,
launchingTaskId = taskInfo.taskId,
remoteTransition = remoteTransition,
+ deskId = deskId,
displayId = taskInfo.displayId,
unminimizeReason = unminimizeReason,
)
@@ -1098,14 +1057,22 @@ class DesktopTasksController(
wct: WindowContainerTransaction,
launchingTaskId: Int?,
remoteTransition: RemoteTransition? = null,
- displayId: Int = DEFAULT_DISPLAY,
+ deskId: Int,
+ displayId: Int,
unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN,
): IBinder {
+ logV(
+ "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d",
+ WindowManager.transitTypeToString(transitionType),
+ launchingTaskId,
+ deskId,
+ displayId,
+ )
// TODO: b/397619806 - Consolidate sharable logic with [handleFreeformTaskLaunch].
var launchTransaction = wct
val taskIdToMinimize =
addAndGetMinimizeChanges(
- displayId,
+ deskId,
launchTransaction,
newTaskId = launchingTaskId,
launchingNewIntent = launchingTaskId == null,
@@ -1119,29 +1086,23 @@ class DesktopTasksController(
)
var activationRunOnTransitStart: RunOnTransitStart? = null
val shouldActivateDesk =
- (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue ||
- DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) &&
- !isDesktopModeShowing(displayId)
+ when {
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ->
+ !taskRepository.isDeskActive(deskId)
+ DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue -> {
+ !isDesktopModeShowing(displayId)
+ }
+ else -> false
+ }
if (shouldActivateDesk) {
- val deskIdToActivate =
- checkNotNull(
- launchingTaskId?.let { taskRepository.getDeskIdForTask(it) }
- ?: getDefaultDeskId(displayId)
- )
val activateDeskWct = WindowContainerTransaction()
- addDeskActivationChanges(deskIdToActivate, activateDeskWct)
+ // TODO: b/391485148 - pass in the launching task here to apply task-limit policy,
+ // but make sure to not do it twice since it is also done at the start of this
+ // function.
+ activationRunOnTransitStart = addDeskActivationChanges(deskId, activateDeskWct)
// Desk activation must be handled before app launch-related transactions.
activateDeskWct.merge(launchTransaction, /* transfer= */ true)
launchTransaction = activateDeskWct
- activationRunOnTransitStart = { transition ->
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActivateDesk(
- token = transition,
- displayId = displayId,
- deskId = deskIdToActivate,
- )
- )
- }
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
@@ -1249,7 +1210,14 @@ class DesktopTasksController(
}
wct.sendPendingIntent(pendingIntent, intent, ops.toBundle())
- startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+ val deskId = getDefaultDeskId(displayId)
+ startLaunchTransition(
+ TRANSIT_OPEN,
+ wct,
+ launchingTaskId = null,
+ deskId = deskId,
+ displayId = displayId,
+ )
}
/**
@@ -1266,6 +1234,11 @@ class DesktopTasksController(
return
}
+ if (splitScreenController.isTaskInSplitScreen(task.taskId)) {
+ moveSplitPairToDisplay(task, displayId)
+ return
+ }
+
val wct = WindowContainerTransaction()
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
@@ -1273,38 +1246,6 @@ class DesktopTasksController(
return
}
- // check if the task is part of splitscreen
- if (
- Flags.enableNonDefaultDisplaySplit() &&
- Flags.enableMoveToNextDisplayShortcut() &&
- splitScreenController.isTaskInSplitScreen(task.taskId)
- ) {
- val activeDeskId = taskRepository.getActiveDeskId(displayId)
- logV("moveToDisplay: moving split root to displayId=%d", displayId)
- val stageCoordinatorRootTaskToken =
- splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId(
- DEFAULT_DISPLAY
- )
- wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */)
- val deactivationRunnable =
- if (activeDeskId != null) {
- // Split is being placed on top of an existing desk in the target display. Make
- // sure it is cleaned up.
- performDesktopExitCleanUp(
- wct = wct,
- deskId = activeDeskId,
- displayId = displayId,
- willExitDesktop = true,
- shouldEndUpAtHome = false,
- )
- } else {
- null
- }
- val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
- deactivationRunnable?.invoke(transition)
- return
- }
-
val destinationDeskId = taskRepository.getDefaultDeskId(displayId)
if (destinationDeskId == null) {
logW("moveToDisplay: desk not found for display: $displayId")
@@ -1327,17 +1268,7 @@ class DesktopTasksController(
wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true)
}
- addDeskActivationChanges(destinationDeskId, wct)
- val activationRunnable: RunOnTransitStart = { transition ->
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = displayId,
- deskId = destinationDeskId,
- enterTaskId = task.taskId,
- )
- )
- }
+ val activationRunnable = addDeskActivationChanges(destinationDeskId, wct, task)
if (Flags.enableDisplayFocusInShellTransitions()) {
// Bring the destination display to top with includingParents=true, so that the
@@ -1368,12 +1299,60 @@ class DesktopTasksController(
} else {
null
}
- val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ val transition =
+ transitions.startTransition(TRANSIT_CHANGE, wct, moveToDisplayTransitionHandler)
deactivationRunnable?.invoke(transition)
activationRunnable?.invoke(transition)
}
/**
+ * Move split pair associated with the [task] to display with [displayId].
+ *
+ * No-op if task is already on that display per [RunningTaskInfo.displayId].
+ */
+ private fun moveSplitPairToDisplay(task: RunningTaskInfo, displayId: Int) {
+ if (!splitScreenController.isTaskInSplitScreen(task.taskId)) {
+ return
+ }
+
+ if (!Flags.enableNonDefaultDisplaySplit() || !Flags.enableMoveToNextDisplayShortcut()) {
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+ if (displayAreaInfo == null) {
+ logW("moveSplitPairToDisplay: display not found")
+ return
+ }
+
+ val activeDeskId = taskRepository.getActiveDeskId(displayId)
+ logV("moveSplitPairToDisplay: moving split root to displayId=%d", displayId)
+
+ val stageCoordinatorRootTaskToken =
+ splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId(DEFAULT_DISPLAY)
+ wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */)
+
+ val deactivationRunnable =
+ if (activeDeskId != null) {
+ // Split is being placed on top of an existing desk in the target display. Make
+ // sure it is cleaned up.
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = activeDeskId,
+ displayId = displayId,
+ willExitDesktop = true,
+ shouldEndUpAtHome = false,
+ )
+ } else {
+ null
+ }
+ val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ deactivationRunnable?.invoke(transition)
+ return
+ }
+
+ /**
* Quick-resizes a desktop task, toggling between a fullscreen state (represented by the stable
* bounds) and a free floating state (either the last saved bounds if available or the default
* bounds otherwise).
@@ -1733,13 +1712,10 @@ class DesktopTasksController(
}
}
- private fun bringDesktopAppsToFrontBeforeShowingNewTask(
- displayId: Int,
- wct: WindowContainerTransaction,
- newTaskIdInFront: Int,
- ): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
-
- @Deprecated("Use activeDesk() instead.", ReplaceWith("activateDesk()"))
+ @Deprecated(
+ "Use addDeskActivationChanges() instead.",
+ ReplaceWith("addDeskActivationChanges()"),
+ )
private fun bringDesktopAppsToFront(
displayId: Int,
wct: WindowContainerTransaction,
@@ -1771,15 +1747,7 @@ class DesktopTasksController(
wct.reorder(runningTaskInfo.token, /* onTop= */ true)
} else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
// Task is not running, start it
- wct.startTask(
- taskId,
- ActivityOptions.makeBasic()
- .apply {
- launchWindowingMode = WINDOWING_MODE_FREEFORM
- splashScreenStyle = SPLASH_SCREEN_STYLE_ICON
- }
- .toBundle(),
- )
+ wct.startTask(taskId, createActivityOptionsForStartTask().toBundle())
}
}
@@ -1798,34 +1766,7 @@ class DesktopTasksController(
}
private fun addLaunchHomePendingIntent(wct: WindowContainerTransaction, displayId: Int) {
- val userHandle = UserHandle.of(userId)
- val launchHomeIntent =
- Intent(Intent.ACTION_MAIN).apply {
- if (displayId != DEFAULT_DISPLAY) {
- addCategory(Intent.CATEGORY_SECONDARY_HOME)
- } else {
- addCategory(Intent.CATEGORY_HOME)
- }
- }
- val options =
- ActivityOptions.makeBasic().apply {
- launchWindowingMode = WINDOWING_MODE_FULLSCREEN
- pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
- launchDisplayId = displayId
- }
- }
- val pendingIntent =
- PendingIntent.getActivityAsUser(
- context,
- /* requestCode= */ 0,
- launchHomeIntent,
- PendingIntent.FLAG_IMMUTABLE,
- /* options= */ null,
- userHandle,
- )
- wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle())
+ homeIntentProvider.addLaunchHomePendingIntent(wct, displayId, userId)
}
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
@@ -1913,8 +1854,8 @@ class DesktopTasksController(
): Boolean {
if (
forceExitDesktop &&
- (Flags.enableDesktopWindowingPip() ||
- DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue)
+ (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue)
) {
// |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
// explicitly going fullscreen, so there's no point in checking the desktop state.
@@ -1924,11 +1865,6 @@ class DesktopTasksController(
if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId, displayId)) {
return false
}
- } else if (
- Flags.enableDesktopWindowingPip() &&
- taskRepository.isMinimizedPipPresentInDisplay(displayId)
- ) {
- return false
} else {
if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId)) {
return false
@@ -1937,6 +1873,33 @@ class DesktopTasksController(
return true
}
+ /** Potentially perform Desktop cleanup after a task successfully enters PiP. */
+ @VisibleForTesting
+ fun onDesktopTaskEnteredPip(
+ taskId: Int,
+ deskId: Int,
+ displayId: Int,
+ taskIsLastVisibleTaskBeforePip: Boolean,
+ ) {
+ if (
+ !willExitDesktop(taskId, displayId, forceExitDesktop = taskIsLastVisibleTaskBeforePip)
+ ) {
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = true,
+ )
+
+ val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ desktopExitRunnable?.invoke(transition)
+ }
+
private fun performDesktopExitCleanupIfNeeded(
taskId: Int,
deskId: Int? = null,
@@ -1945,7 +1908,6 @@ class DesktopTasksController(
forceToFullscreen: Boolean,
shouldEndUpAtHome: Boolean = true,
): RunOnTransitStart? {
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = !forceToFullscreen)
if (!willExitDesktop(taskId, displayId, forceToFullscreen)) {
return null
}
@@ -2220,6 +2182,7 @@ class DesktopTasksController(
// TODO(b/337915660): Add a transition handler for these; animations
// need updates in some cases.
val baseActivity = callingTaskInfo.baseActivity ?: return
+ val userHandle = UserHandle.of(callingTaskInfo.userId)
val fillIn: Intent =
userProfileContexts
.getOrCreate(callingTaskInfo.userId)
@@ -2227,11 +2190,13 @@ class DesktopTasksController(
.getLaunchIntentForPackage(baseActivity.packageName) ?: return
fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val launchIntent =
- PendingIntent.getActivity(
+ PendingIntent.getActivityAsUser(
context,
/* requestCode= */ 0,
fillIn,
PendingIntent.FLAG_IMMUTABLE,
+ /* options= */ null,
+ userHandle,
)
val options = createNewWindowOptions(callingTaskInfo)
when (options.launchWindowingMode) {
@@ -2258,10 +2223,14 @@ class DesktopTasksController(
WINDOWING_MODE_FREEFORM -> {
val wct = WindowContainerTransaction()
wct.sendPendingIntent(launchIntent, fillIn, options.toBundle())
+ val deskId =
+ taskRepository.getDeskIdForTask(callingTaskInfo.taskId)
+ ?: getDefaultDeskId(callingTaskInfo.displayId)
startLaunchTransition(
transitionType = TRANSIT_OPEN,
wct = wct,
launchingTaskId = null,
+ deskId = deskId,
displayId = callingTaskInfo.displayId,
)
}
@@ -2341,6 +2310,7 @@ class DesktopTasksController(
logV("skip keyguard is locked")
return null
}
+ val deskId = getDefaultDeskId(task.displayId)
val wct = WindowContainerTransaction()
if (shouldFreeformTaskLaunchSwitchToFullscreen(task)) {
logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId)
@@ -2363,15 +2333,24 @@ class DesktopTasksController(
runOnTransitStart?.invoke(transition)
return wct
}
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
+ runOnTransitStart?.invoke(transition)
wct.reorder(task.token, true)
return wct
}
+ val inheritedTaskBounds =
+ getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId)
+ if (!taskRepository.isActiveTask(task.taskId) && inheritedTaskBounds != null) {
+ // Inherit bounds from closing task instance to prevent application jumping different
+ // cascading positions.
+ wct.setBounds(task.token, inheritedTaskBounds)
+ }
// TODO(b/365723620): Handle non running tasks that were launched after reboot.
// If task is already visible, it must have been handled already and added to desktop mode.
- // Cascade task only if it's not visible yet.
+ // Cascade task only if it's not visible yet and has no inherited bounds.
if (
- DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
+ inheritedTaskBounds == null &&
+ DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
!taskRepository.isVisibleTask(task.taskId)
) {
val displayLayout = displayController.getDisplayLayout(task.displayId)
@@ -2403,7 +2382,7 @@ class DesktopTasksController(
reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
// 2) minimize a Task if needed.
- val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+ val taskIdToMinimize = addAndGetMinimizeChanges(deskId, wct, task.taskId)
addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
if (taskIdToMinimize != null) {
addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
@@ -2426,25 +2405,42 @@ class DesktopTasksController(
return WindowContainerTransaction().also { wct ->
val deskId = getDefaultDeskId(task.displayId)
addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
- // In some launches home task is moved behind new task being launched. Make sure
- // that's not the case for launches in desktop. Also, if this launch is the first
- // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the
- // desktop mode here.
- if (
- task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 ||
- !isDesktopModeShowing(task.displayId)
- ) {
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
- wct.reorder(task.token, true)
- }
-
- // Desktop Mode is already showing and we're launching a new Task - we might need to
- // minimize another Task.
- val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
- addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
+ val runOnTransitStart: RunOnTransitStart? =
+ if (
+ task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 ||
+ !isDesktopModeShowing(task.displayId)
+ ) {
+ // In some launches home task is moved behind new task being launched. Make
+ // sure that's not the case for launches in desktop. Also, if this launch is
+ // the first one to trigger the desktop mode (e.g., when
+ // [forceEnterDesktop()]), activate the desk here.
+ val activationRunnable =
+ addDeskActivationChanges(
+ deskId = deskId,
+ wct = wct,
+ newTask = task,
+ addPendingLaunchTransition = true,
+ )
+ wct.reorder(task.token, true)
+ activationRunnable
+ } else {
+ { transition: IBinder ->
+ // The desk was already showing and we're launching a new Task - we
+ // might need to minimize another Task.
+ val taskIdToMinimize =
+ addAndGetMinimizeChanges(deskId, wct, task.taskId)
+ taskIdToMinimize?.let { minimizingTaskId ->
+ addPendingMinimizeTransition(
+ transition,
+ minimizingTaskId,
+ MinimizeReason.TASK_LIMIT,
+ )
+ }
+ // Also track the pending launching task.
+ addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
+ }
+ }
+ runOnTransitStart?.invoke(transition)
desktopImmersiveController.exitImmersiveIfApplicable(
transition,
wct,
@@ -2559,6 +2555,20 @@ class DesktopTasksController(
}
/**
+ * Applies the [wct] changes need when a task is first moving to a desk and the desk needs to be
+ * activated.
+ */
+ private fun addDeskActivationWithMovingTaskChanges(
+ deskId: Int,
+ wct: WindowContainerTransaction,
+ task: RunningTaskInfo,
+ ): RunOnTransitStart? {
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
+ return runOnTransitStart
+ }
+
+ /**
* Applies the [wct] changes needed when a task is first moving to a desk.
*
* Note that this recalculates the initial bounds of the task, so it should not be used when
@@ -2576,9 +2586,17 @@ class DesktopTasksController(
) {
val targetDisplayId = taskRepository.getDisplayForDesk(deskId)
val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return
- val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId)
- if (canChangeTaskPosition(task)) {
- wct.setBounds(task.token, initialBounds)
+ val inheritedTaskBounds =
+ getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId)
+ if (inheritedTaskBounds != null) {
+ // Inherit bounds from closing task instance to prevent application jumping different
+ // cascading positions.
+ wct.setBounds(task.token, inheritedTaskBounds)
+ } else {
+ val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId)
+ if (canChangeTaskPosition(task)) {
+ wct.setBounds(task.token, initialBounds)
+ }
}
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task)
@@ -2656,7 +2674,7 @@ class DesktopTasksController(
// Caption insets stay fixed and don't scale with bounds.
val captionInsets =
if (desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo)) {
- getAppHeaderHeight(context)
+ getDesktopViewAppHeaderHeightPx(context)
} else {
0
}
@@ -2697,7 +2715,6 @@ class DesktopTasksController(
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
wct.reparent(taskInfo.token, tdaInfo.token, /* onTop= */ true)
}
- taskRepository.setPipShouldKeepDesktopActive(taskInfo.displayId, keepActive = false)
val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
return performDesktopExitCleanUp(
wct = wct,
@@ -2755,7 +2772,7 @@ class DesktopTasksController(
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
private fun addAndGetMinimizeChanges(
- displayId: Int,
+ deskId: Int,
wct: WindowContainerTransaction,
newTaskId: Int?,
launchingNewIntent: Boolean = false,
@@ -2764,7 +2781,7 @@ class DesktopTasksController(
require(newTaskId == null || !launchingNewIntent)
return desktopTasksLimiter
.get()
- .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent)
+ .addAndGetMinimizeTaskChanges(deskId, wct, newTaskId, launchingNewIntent)
}
private fun addPendingMinimizeTransition(
@@ -2826,28 +2843,98 @@ class DesktopTasksController(
activateDesk(deskId, remoteTransition)
}
- /** Activates the given desk but without starting a transition. */
- fun addDeskActivationChanges(deskId: Int, wct: WindowContainerTransaction) {
+ /**
+ * Applies the necessary [wct] changes to activate the given desk.
+ *
+ * When a task is being brought into a desk together with the activation, then [newTask] is not
+ * null and may be used to run other desktop policies, such as minimizing another task if the
+ * task limit has been exceeded.
+ */
+ fun addDeskActivationChanges(
+ deskId: Int,
+ wct: WindowContainerTransaction,
+ newTask: TaskInfo? = null,
+ // TODO: b/362720497 - should this be true in other places? Can it be calculated locally
+ // without having to specify the value?
+ addPendingLaunchTransition: Boolean = false,
+ ): RunOnTransitStart? {
+ logV("addDeskActivationChanges newTaskId=%d deskId=%d", newTask?.taskId, deskId)
+ val newTaskIdInFront = newTask?.taskId
val displayId = taskRepository.getDisplayForDesk(deskId)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- prepareForDeskActivation(displayId, wct)
- desksOrganizer.activateDesk(wct, deskId)
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
- // TODO: 362720497 - do non-running tasks need to be restarted with |wct#startTask|?
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ val taskIdToMinimize = bringDesktopAppsToFront(displayId, wct, newTask?.taskId)
+ return { transition ->
+ taskIdToMinimize?.let { minimizingTaskId ->
+ addPendingMinimizeTransition(
+ transition = transition,
+ taskIdToMinimize = minimizingTaskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ }
+ if (newTask != null && addPendingLaunchTransition) {
+ addPendingAppLaunchTransition(transition, newTask.taskId, taskIdToMinimize)
+ }
+ }
+ }
+ prepareForDeskActivation(displayId, wct)
+ desksOrganizer.activateDesk(wct, deskId)
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(displayId)
+ )
+ val expandedTasksOrderedFrontToBack =
+ taskRepository.getExpandedTasksIdsInDeskOrdered(deskId = deskId)
+ // If we're adding a new Task we might need to minimize an old one
+ val taskIdToMinimize =
+ desktopTasksLimiter
+ .getOrNull()
+ ?.getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
+ if (taskIdToMinimize != null) {
+ val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
+ // TODO(b/365725441): Handle non running task minimization
+ if (taskToMinimize != null) {
+ desksOrganizer.minimizeTask(wct, deskId, taskToMinimize)
+ }
+ }
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) {
+ expandedTasksOrderedFrontToBack
+ .filter { taskId -> taskId != taskIdToMinimize }
+ .reversed()
+ .forEach { taskId ->
+ val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ if (runningTaskInfo == null) {
+ wct.startTask(taskId, createActivityOptionsForStartTask().toBundle())
+ } else {
+ desksOrganizer.reorderTaskToFront(wct, deskId, runningTaskInfo)
+ }
+ }
+ }
+ return { transition ->
+ val activateDeskTransition =
+ if (newTaskIdInFront != null) {
+ DeskTransition.ActiveDeskWithTask(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ enterTaskId = newTaskIdInFront,
+ )
+ } else {
+ DeskTransition.ActivateDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ )
+ }
+ desksTransitionObserver.addPendingTransition(activateDeskTransition)
+ taskIdToMinimize?.let { minimizingTask ->
+ addPendingMinimizeTransition(transition, minimizingTask, MinimizeReason.TASK_LIMIT)
}
- taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
- doesAnyTaskRequireTaskbarRounding(displayId)
- )
- } else {
- bringDesktopAppsToFront(displayId, wct)
}
}
/** Activates the given desk. */
fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) {
- val displayId = taskRepository.getDisplayForDesk(deskId)
val wct = WindowContainerTransaction()
- addDeskActivationChanges(deskId, wct)
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct)
val transitionType = transitionType(remoteTransition)
val handler =
@@ -2857,15 +2944,7 @@ class DesktopTasksController(
val transition = transitions.startTransition(transitionType, wct, handler)
handler?.setTransition(transition)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActivateDesk(
- token = transition,
- displayId = displayId,
- deskId = deskId,
- )
- )
- }
+ runOnTransitStart?.invoke(transition)
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
@@ -2907,6 +2986,11 @@ class DesktopTasksController(
removeDesk(displayId = displayId, deskId = deskId)
}
+ /** Removes all the available desks on all displays. */
+ fun removeAllDesks() {
+ taskRepository.getAllDeskIds().forEach { deskId -> removeDesk(deskId) }
+ }
+
private fun removeDesk(displayId: Int, deskId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
@@ -3428,7 +3512,14 @@ class DesktopTasksController(
if (windowingMode == WINDOWING_MODE_FREEFORM) {
if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) {
// TODO b/376389593: Use a custom tab tearing transition/animation
- startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+ val deskId = getDefaultDeskId(DEFAULT_DISPLAY)
+ startLaunchTransition(
+ TRANSIT_OPEN,
+ wct,
+ launchingTaskId = null,
+ deskId = deskId,
+ displayId = DEFAULT_DISPLAY,
+ )
} else {
desktopModeDragAndDropTransitionHandler.handleDropEvent(wct)
}
@@ -3473,6 +3564,13 @@ class DesktopTasksController(
}
}
+ private fun createActivityOptionsForStartTask(): ActivityOptions {
+ return ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ splashScreenStyle = SPLASH_SCREEN_STYLE_ICON
+ }
+ }
+
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
@@ -3659,6 +3757,18 @@ class DesktopTasksController(
}
}
+ override fun removeDesk(deskId: Int) {
+ executeRemoteCallWithTaskPermission(controller, "removeDesk") { c ->
+ c.removeDesk(deskId)
+ }
+ }
+
+ override fun removeAllDesks() {
+ executeRemoteCallWithTaskPermission(controller, "removeAllDesks") { c ->
+ c.removeAllDesks()
+ }
+ }
+
override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) {
executeRemoteCallWithTaskPermission(controller, "activateDesk") { c ->
c.activateDesk(deskId, remoteTransition)
@@ -3722,8 +3832,8 @@ class DesktopTasksController(
}
}
- override fun removeDesktop(displayId: Int) {
- executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
+ override fun removeDefaultDeskInDisplay(displayId: Int) {
+ executeRemoteCallWithTaskPermission(controller, "removeDefaultDeskInDisplay") { c ->
c.removeDefaultDeskInDisplay(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index f9ab359e952d..4ca58823b52b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -22,6 +22,7 @@ import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
@@ -31,6 +32,7 @@ import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.UserChangeListener
@@ -38,17 +40,18 @@ import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
/**
- * Limits the number of tasks shown in Desktop Mode.
+ * Keeps track of minimized tasks and limits the number of tasks shown in Desktop Mode.
*
- * This class should only be used if
- * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] is enabled and
- * [maxTasksLimit] is strictly greater than 0.
+ * [maxTasksLimit] must be strictly greater than 0 if it's given.
+ *
+ * TODO(b/400634379): Separate two responsibilities of this class into two classes.
*/
class DesktopTasksLimiter(
transitions: Transitions,
private val desktopUserRepositories: DesktopUserRepositories,
private val shellTaskOrganizer: ShellTaskOrganizer,
- private val maxTasksLimit: Int,
+ private val desksOrganizer: DesksOrganizer,
+ private val maxTasksLimit: Int?,
private val interactionJankMonitor: InteractionJankMonitor,
private val context: Context,
@ShellMainThread private val handler: Handler,
@@ -59,13 +62,19 @@ class DesktopTasksLimiter(
private var userId: Int
init {
- require(maxTasksLimit > 0) {
- "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $maxTasksLimit."
+ maxTasksLimit?.let {
+ require(it > 0) {
+ "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $it."
+ }
}
transitions.registerObserver(minimizeTransitionObserver)
userId = ActivityManager.getCurrentUser()
desktopUserRepositories.current.addActiveTaskListener(leftoverMinimizedTasksRemover)
- logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
+ if (maxTasksLimit != null) {
+ logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
+ } else {
+ logV("Starting limiter without the task limit")
+ }
}
data class TaskDetails(
@@ -252,7 +261,7 @@ class DesktopTasksLimiter(
* returning the task to minimize.
*/
fun addAndGetMinimizeTaskChanges(
- displayId: Int,
+ deskId: Int,
wct: WindowContainerTransaction,
newFrontTaskId: Int?,
launchingNewIntent: Boolean = false,
@@ -261,14 +270,19 @@ class DesktopTasksLimiter(
val taskRepository = desktopUserRepositories.current
val taskIdToMinimize =
getTaskIdToMinimize(
- taskRepository.getExpandedTasksOrdered(displayId),
+ taskRepository.getExpandedTasksIdsInDeskOrdered(deskId),
newFrontTaskId,
launchingNewIntent,
)
- // If it's a running task, reorder it to back.
taskIdToMinimize
?.let { shellTaskOrganizer.getRunningTaskInfo(it) }
- ?.let { wct.reorder(it.token, /* onTop= */ false) }
+ ?.let { task ->
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ wct.reorder(task.token, /* onTop= */ false)
+ } else {
+ desksOrganizer.minimizeTask(wct, deskId, task)
+ }
+ }
return taskIdToMinimize
}
@@ -324,7 +338,7 @@ class DesktopTasksLimiter(
launchingNewIntent: Boolean,
): Int? {
val newTasksOpening = if (launchingNewIntent) 1 else 0
- if (visibleOrderedTasks.size + newTasksOpening <= maxTasksLimit) {
+ if (visibleOrderedTasks.size + newTasksOpening <= (maxTasksLimit ?: Int.MAX_VALUE)) {
logV("No need to minimize; tasks below limit")
// No need to minimize anything
return null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index df2cf67fced2..df4d18f8c803 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,9 +23,7 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
-import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER
@@ -33,19 +31,18 @@ import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVI
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
-import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.TransitionUtil.isClosingMode
+import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
-import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import java.util.Optional
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
@@ -58,17 +55,15 @@ class DesktopTasksTransitionObserver(
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
+ private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
- private val desksTransitionObserver: DesksTransitionObserver,
shellInit: ShellInit,
) : Transitions.TransitionObserver {
data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
- /* Pending PiP transition and its associated display id and task id. */
- private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
private var currentProfileId: Int
init {
@@ -90,7 +85,6 @@ class DesktopTasksTransitionObserver(
finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
- desksTransitionObserver.onTransitionReady(transition, info)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
@@ -103,33 +97,7 @@ class DesktopTasksTransitionObserver(
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
-
- val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
- info.changes.forEach { change ->
- change.taskInfo?.let { taskInfo ->
- if (
- Flags.enableDesktopWindowingPip() &&
- desktopRepository.isTaskMinimizedPipInDisplay(
- taskInfo.displayId,
- taskInfo.taskId,
- )
- ) {
- when (info.type) {
- TRANSIT_PIP ->
- pendingPipTransitionAndPipTask =
- Triple(transition, taskInfo.displayId, taskInfo.taskId)
-
- TRANSIT_EXIT_PIP,
- TRANSIT_REMOVE_PIP ->
- desktopRepository.setTaskInPip(
- taskInfo.displayId,
- taskInfo.taskId,
- enterPip = false,
- )
- }
- }
- }
- }
+ desktopPipTransitionObserver.ifPresent { it.onTransitionReady(transition, info) }
}
private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -304,18 +272,6 @@ class DesktopTasksTransitionObserver(
}
}
transitionToCloseWallpaper = null
- } else if (pendingPipTransitionAndPipTask?.first == transition) {
- val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
- if (aborted) {
- pendingPipTransitionAndPipTask?.let {
- desktopRepository.onPipAborted(
- /*displayId=*/ it.second,
- /* taskId=*/ it.third,
- )
- }
- }
- desktopRepository.setOnPipAbortedCallback(null)
- pendingPipTransitionAndPipTask = null
}
}
@@ -332,10 +288,6 @@ class DesktopTasksTransitionObserver(
taskInfo.token,
taskInfo.displayId,
)
- desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
- isVisible = true,
- taskInfo.displayId,
- )
// After the task for the wallpaper is created, set it non-trimmable.
// This is important to prevent recents from trimming and removing the
// task.
@@ -346,16 +298,6 @@ class DesktopTasksTransitionObserver(
}
TRANSIT_CLOSE ->
desktopWallpaperActivityTokenProvider.removeToken(taskInfo.displayId)
- TRANSIT_TO_FRONT ->
- desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
- isVisible = true,
- taskInfo.displayId,
- )
- TRANSIT_TO_BACK ->
- desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
- isVisible = false,
- taskInfo.displayId,
- )
else -> {}
}
}
@@ -364,18 +306,29 @@ class DesktopTasksTransitionObserver(
}
private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) {
- info.changes.forEach { change ->
- change.taskInfo?.let { task ->
- val desktopRepository = desktopUserRepositories.getProfile(task.userId)
- val displayId = task.displayId
- // Clear `topTransparentFullscreenTask` information from repository if task
- // is closed or sent to back.
- if (
- TransitionUtil.isClosingMode(change.mode) &&
- task.taskId ==
- desktopRepository.getTopTransparentFullscreenTaskId(displayId)
- ) {
- desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ run forEachLoop@{
+ info.changes.forEach { change ->
+ change.taskInfo?.let { task ->
+ val desktopRepository = desktopUserRepositories.getProfile(task.userId)
+ val displayId = task.displayId
+ val transparentTaskId =
+ desktopRepository.getTopTransparentFullscreenTaskId(displayId)
+ if (transparentTaskId == null) return@forEachLoop
+ val changeMode = change.mode
+ val taskId = task.taskId
+ val isTopTransparentFullscreenTaskClosing =
+ taskId == transparentTaskId && isClosingMode(changeMode)
+ val isNonTopTransparentFullscreenTaskOpening =
+ taskId != transparentTaskId && isOpeningMode(changeMode)
+ // Clear `topTransparentFullscreenTask` information from repository if task
+ // is closed, sent to back or if a different task is opened, brought to front.
+ if (
+ isTopTransparentFullscreenTaskClosing ||
+ isNonTopTransparentFullscreenTaskOpening
+ ) {
+ desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ return@forEachLoop
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index d396d8bff2b8..c6f74728fd81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -2,6 +2,7 @@ package com.android.wm.shell.desktopmode
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
@@ -23,8 +24,12 @@ import android.os.IBinder
import android.os.SystemClock
import android.os.SystemProperties
import android.os.UserHandle
+import android.view.Choreographer
import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.DesktopModeFlags
+import android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
@@ -45,6 +50,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -118,6 +124,8 @@ sealed class DragToDesktopTransitionHandler(
fun startDragToDesktopTransition(
taskInfo: RunningTaskInfo,
dragToDesktopAnimator: MoveToDesktopAnimator,
+ visualIndicator: DesktopModeVisualIndicator?,
+ dragCancelCallback: Runnable,
) {
if (inProgress) {
logV("Drag to desktop transition already in progress.")
@@ -163,12 +171,16 @@ sealed class DragToDesktopTransitionHandler(
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask,
+ visualIndicator = visualIndicator,
+ dragCancelCallback = dragCancelCallback,
)
} else {
TransitionState.FromFullscreen(
draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
+ visualIndicator = visualIndicator,
+ dragCancelCallback = dragCancelCallback,
)
}
}
@@ -181,18 +193,30 @@ sealed class DragToDesktopTransitionHandler(
*/
fun finishDragToDesktopTransition(wct: WindowContainerTransaction): IBinder? {
if (!inProgress) {
+ logV("finishDragToDesktop: not in progress, returning")
// Don't attempt to finish a drag to desktop transition since there is no transition in
// progress which means that the drag to desktop transition was never successfully
// started.
return null
}
- if (requireTransitionState().startAborted) {
+ val state = requireTransitionState()
+ if (state.startAborted) {
+ logV("finishDragToDesktop: start was aborted, clearing state")
// Don't attempt to complete the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
clearState()
return null
}
- return transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
+ if (state.startInterrupted) {
+ logV("finishDragToDesktop: start was interrupted, returning")
+ // If start was interrupted we've either already requested a cancel/end transition - so
+ // we should let that request play out, or we're cancelling the drag-to-desktop
+ // transition altogether, so just return here.
+ return null
+ }
+ state.endTransitionToken =
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
+ return state.endTransitionToken
}
/**
@@ -204,6 +228,7 @@ sealed class DragToDesktopTransitionHandler(
*/
fun cancelDragToDesktopTransition(cancelState: CancelState) {
if (!inProgress) {
+ logV("cancelDragToDesktop: not in progress, returning")
// Don't attempt to cancel a drag to desktop transition since there is no transition in
// progress which means that the drag to desktop transition was never successfully
// started.
@@ -211,11 +236,19 @@ sealed class DragToDesktopTransitionHandler(
}
val state = requireTransitionState()
if (state.startAborted) {
+ logV("cancelDragToDesktop: start was aborted, clearing state")
// Don't attempt to cancel the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
clearState()
return
}
+ if (state.startInterrupted) {
+ logV("cancelDragToDesktop: start was interrupted, returning")
+ // If start was interrupted we've either already requested a cancel/end transition - so
+ // we should let that request play out, or we're cancelling the drag-to-desktop
+ // transition altogether, so just return here.
+ return
+ }
state.cancelState = cancelState
if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) {
@@ -223,7 +256,7 @@ sealed class DragToDesktopTransitionHandler(
// transient to start and merge. Animate the cancellation (scale back to original
// bounds) first before actually starting the cancel transition so that the wallpaper
// is visible behind the animating task.
- startCancelAnimation()
+ state.activeCancelAnimation = startCancelAnimation()
} else if (
state.draggedTaskChange != null &&
(cancelState == CancelState.CANCEL_SPLIT_LEFT ||
@@ -251,7 +284,7 @@ sealed class DragToDesktopTransitionHandler(
) {
if (bubbleController.isEmpty || state !is TransitionState.FromFullscreen) {
// TODO(b/388853233): add support for dragging split task to bubble
- startCancelAnimation()
+ state.activeCancelAnimation = startCancelAnimation()
} else {
// Animation is handled by BubbleController
val wct = WindowContainerTransaction()
@@ -277,6 +310,7 @@ sealed class DragToDesktopTransitionHandler(
val state = requireTransitionState()
val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
val animatedTaskBounds = getAnimatedTaskBounds()
+ state.dragAnimator.cancelAnimator()
requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
}
@@ -288,7 +322,6 @@ sealed class DragToDesktopTransitionHandler(
val scaledWidth = taskBounds.width() * taskScale
val scaledHeight = taskBounds.height() * taskScale
val dragPosition = PointF(state.dragAnimator.position)
- state.dragAnimator.cancelAnimator()
return Rect(
dragPosition.x.toInt(),
dragPosition.y.toInt(),
@@ -321,22 +354,26 @@ sealed class DragToDesktopTransitionHandler(
// TODO(b/391928049): update density once we can drag from desktop to bubble
val state = requireTransitionState()
val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
- val taskBounds = getAnimatedTaskBounds()
+ val dragPosition = PointF(state.dragAnimator.position)
+ val scale = state.dragAnimator.scale
+ val cornerRadius = state.dragAnimator.cornerRadius
state.dragAnimator.cancelAnimator()
- requestBubble(wct, taskInfo, onLeft, taskBounds)
+ requestBubble(wct, taskInfo, onLeft, scale, cornerRadius, dragPosition)
}
private fun requestBubble(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo,
onLeft: Boolean,
- taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds),
+ taskScale: Float = 1f,
+ cornerRadius: Float = 0f,
+ dragPosition: PointF = PointF(0f, 0f),
) {
val controller =
bubbleController.orElseThrow { IllegalStateException("BubbleController not set") }
controller.expandStackAndSelectBubble(
taskInfo,
- BubbleTransitions.DragData(taskBounds, wct, onLeft),
+ BubbleTransitions.DragData(onLeft, taskScale, cornerRadius, dragPosition, wct),
)
}
@@ -349,6 +386,19 @@ sealed class DragToDesktopTransitionHandler(
): Boolean {
val state = requireTransitionState()
+ if (
+ handleCancelOrExitAfterInterrupt(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ state,
+ )
+ ) {
+ return true
+ }
+
val isStartDragToDesktop =
info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP &&
transition == state.startTransitionToken
@@ -457,6 +507,13 @@ sealed class DragToDesktopTransitionHandler(
state.surfaceLayers = layers
state.startTransitionFinishCb = finishCallback
state.startTransitionFinishTransaction = finishTransaction
+
+ val taskChange = state.draggedTaskChange ?: error("Expected non-null task change.")
+ val taskInfo = taskChange.taskInfo ?: error("Expected non-null task info.")
+
+ if (DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue) {
+ attachIndicatorToTransitionRoot(state, info, taskInfo, startTransaction)
+ }
startTransaction.apply()
if (state.cancelState == CancelState.NO_CANCEL) {
@@ -485,8 +542,6 @@ sealed class DragToDesktopTransitionHandler(
} else {
SPLIT_POSITION_BOTTOM_OR_RIGHT
}
- val taskInfo =
- state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
val wct = WindowContainerTransaction()
restoreWindowOrder(wct)
state.startTransitionFinishTransaction?.apply()
@@ -511,6 +566,73 @@ sealed class DragToDesktopTransitionHandler(
return true
}
+ private fun attachIndicatorToTransitionRoot(
+ state: TransitionState,
+ info: TransitionInfo,
+ taskInfo: RunningTaskInfo,
+ t: SurfaceControl.Transaction,
+ ) {
+ val transitionRoot = info.getRoot(info.findRootIndex(taskInfo.displayId))
+ state.visualIndicator?.let {
+ // Attach the indicator to the transition root so that it's removed at the end of the
+ // transition regardless of whether we managed to release the indicator.
+ it.reparentLeash(t, transitionRoot.leash)
+ it.fadeInIndicator()
+ }
+ }
+
+ private fun handleCancelOrExitAfterInterrupt(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: Transaction,
+ finishTransaction: Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ state: TransitionState,
+ ): Boolean {
+ if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
+ return false
+ }
+ val isCancelDragToDesktop =
+ info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
+ transition == state.cancelTransitionToken
+ val isEndDragToDesktop =
+ info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP &&
+ transition == state.endTransitionToken
+ // We should only receive cancel or end transitions through startAnimation() if the
+ // start transition was interrupted while a cancel- or end-transition had already
+ // been requested. Finish the cancel/end transition to avoid having to deal with more
+ // incoming transitions, and clear the state for the next start-drag transition.
+ if (!isCancelDragToDesktop && !isEndDragToDesktop) {
+ return false
+ }
+ if (!state.startInterrupted) {
+ logW(
+ "Not interrupted, but received startAnimation for cancel/end drag." +
+ "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop"
+ )
+ return false
+ }
+ logV(
+ "startAnimation: interrupted -> " +
+ "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop"
+ )
+ if (isEndDragToDesktop) {
+ setupEndDragToDesktop(info, startTransaction, finishTransaction)
+ animateEndDragToDesktop(startTransaction = startTransaction, finishCallback)
+ } else { // isCancelDragToDesktop
+ // Similar to when we merge the cancel transition: ensure all tasks involved in the
+ // cancel transition are shown, and finish the transition immediately.
+ info.changes.forEach { change ->
+ startTransaction.show(change.leash)
+ finishTransaction.show(change.leash)
+ }
+ }
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ clearState()
+ return true
+ }
+
/**
* Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
* based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
@@ -562,6 +684,7 @@ sealed class DragToDesktopTransitionHandler(
?: error("Start transition expected to be waiting for merge but wasn't")
if (isEndTransition) {
logV("mergeAnimation: end-transition, target=$mergeTarget")
+ state.mergedEndTransition = true
setupEndDragToDesktop(
info,
startTransaction = startT,
@@ -589,8 +712,93 @@ sealed class DragToDesktopTransitionHandler(
return
}
logW("unhandled merge transition: transitionInfo=$info")
+ // Handle unknown incoming transitions by finishing the start transition. For now, only do
+ // this if we've already requested a cancel- or end transition. If we've already merged the
+ // end-transition, or if the end-transition is running on its own, then just wait until that
+ // finishes instead. If we've merged the cancel-transition we've finished the
+ // start-transition and won't reach this code.
+ if (mergeTarget == state.startTransitionToken && !state.mergedEndTransition) {
+ interruptStartTransition(state)
+ }
}
+ private fun isCancelOrEndTransitionRequested(state: TransitionState): Boolean =
+ state.cancelTransitionToken != null || state.endTransitionToken != null
+
+ private fun interruptStartTransition(state: TransitionState) {
+ if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
+ return
+ }
+ if (isCancelOrEndTransitionRequested(state)) {
+ logV("interruptStartTransition, bookend requested -> finish start transition")
+ // Finish the start-drag transition, we will finish the overall transition properly when
+ // receiving #startAnimation for Cancel/End.
+ state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
+ state.dragAnimator.cancelAnimator()
+ } else {
+ logV("interruptStartTransition, bookend not requested -> animate to Home")
+ // Animate to Home, and then finish the start-drag transition. Since there is no other
+ // (end/cancel) transition requested that will be the end of the overall transition.
+ state.dragAnimator.cancelAnimator()
+ state.dragCancelCallback?.run()
+ createInterruptToHomeAnimator(transactionSupplier.get(), state) {
+ state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
+ clearState()
+ }
+ }
+ state.activeCancelAnimation?.removeAllListeners()
+ state.activeCancelAnimation?.cancel()
+ state.activeCancelAnimation = null
+ // Keep the transition state so we can deal with Cancel/End properly in #startAnimation.
+ state.startInterrupted = true
+ dragToDesktopStateListener?.onTransitionInterrupted()
+ // Cancel CUJs here as they won't be accurate now that an incoming transition is playing.
+ interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+ interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+ LatencyTracker.getInstance(context)
+ .onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG)
+ }
+
+ private fun createInterruptToHomeAnimator(
+ transaction: Transaction,
+ state: TransitionState,
+ endCallback: Runnable,
+ ) {
+ val homeLeash = state.homeChange?.leash ?: error("Expected home leash to be non-null")
+ val draggedTaskLeash =
+ state.draggedTaskChange?.leash ?: error("Expected dragged leash to be non-null")
+ val homeAnimator = createInterruptAlphaAnimator(transaction, homeLeash, toShow = true)
+ val draggedTaskAnimator =
+ createInterruptAlphaAnimator(transaction, draggedTaskLeash, toShow = false)
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(homeAnimator, draggedTaskAnimator)
+ animatorSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ endCallback.run()
+ }
+ }
+ )
+ animatorSet.start()
+ }
+
+ private fun createInterruptAlphaAnimator(
+ transaction: Transaction,
+ leash: SurfaceControl,
+ toShow: Boolean,
+ ) =
+ ValueAnimator.ofFloat(if (toShow) 0f else 1f, if (toShow) 1f else 0f).apply {
+ transaction.show(leash)
+ duration = DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ transaction
+ .setAlpha(leash, animation.animatedValue as Float)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
+ .apply()
+ }
+ }
+
protected open fun setupEndDragToDesktop(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
@@ -755,7 +963,7 @@ sealed class DragToDesktopTransitionHandler(
} ?: false
}
- private fun startCancelAnimation() {
+ private fun startCancelAnimation(): Animator {
val state = requireTransitionState()
val dragToDesktopAnimator = state.dragAnimator
@@ -772,7 +980,7 @@ sealed class DragToDesktopTransitionHandler(
val dx = targetX - x
val dy = targetY - y
val tx: SurfaceControl.Transaction = transactionSupplier.get()
- ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
+ return ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
.setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
.apply {
addUpdateListener { animator ->
@@ -790,6 +998,7 @@ sealed class DragToDesktopTransitionHandler(
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
+ state.activeCancelAnimation = null
dragToDesktopStateListener?.onCancelToDesktopAnimationEnd()
// Start the cancel transition to restore order.
startCancelDragToDesktopTransition()
@@ -882,10 +1091,16 @@ sealed class DragToDesktopTransitionHandler(
val dragLayer: Int,
)
+ /** Listener for various events happening during the DragToDesktop transition. */
interface DragToDesktopStateListener {
+ /** Indicates that the animation into Desktop has started. */
fun onCommitToDesktopAnimationStart()
+ /** Called when the animation to cancel the desktop-drag has finished. */
fun onCancelToDesktopAnimationEnd()
+
+ /** Indicates that the drag-to-desktop transition has been interrupted. */
+ fun onTransitionInterrupted()
}
sealed class TransitionState {
@@ -901,6 +1116,12 @@ sealed class DragToDesktopTransitionHandler(
abstract var surfaceLayers: DragToDesktopLayers?
abstract var cancelState: CancelState
abstract var startAborted: Boolean
+ abstract val visualIndicator: DesktopModeVisualIndicator?
+ abstract var startInterrupted: Boolean
+ abstract var endTransitionToken: IBinder?
+ abstract var mergedEndTransition: Boolean
+ abstract var activeCancelAnimation: Animator?
+ abstract var dragCancelCallback: Runnable?
data class FromFullscreen(
override val draggedTaskId: Int,
@@ -915,6 +1136,12 @@ sealed class DragToDesktopTransitionHandler(
override var surfaceLayers: DragToDesktopLayers? = null,
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
+ override val visualIndicator: DesktopModeVisualIndicator?,
+ override var startInterrupted: Boolean = false,
+ override var endTransitionToken: IBinder? = null,
+ override var mergedEndTransition: Boolean = false,
+ override var activeCancelAnimation: Animator? = null,
+ override var dragCancelCallback: Runnable? = null,
var otherRootChanges: MutableList<Change> = mutableListOf(),
) : TransitionState()
@@ -931,6 +1158,12 @@ sealed class DragToDesktopTransitionHandler(
override var surfaceLayers: DragToDesktopLayers? = null,
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
+ override val visualIndicator: DesktopModeVisualIndicator?,
+ override var startInterrupted: Boolean = false,
+ override var endTransitionToken: IBinder? = null,
+ override var mergedEndTransition: Boolean = false,
+ override var activeCancelAnimation: Animator? = null,
+ override var dragCancelCallback: Runnable? = null,
var splitRootChange: Change? = null,
var otherSplitTask: Int,
) : TransitionState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 44f7e16e98c3..5f7fbd9843d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -35,6 +35,12 @@ interface IDesktopMode {
/** Activates the desk whose ID is `deskId` on whatever display it currently exists on. */
oneway void activateDesk(int deskId, in RemoteTransition remoteTransition);
+ /** Removes the desk with the given `deskId`. */
+ oneway void removeDesk(int deskId);
+
+ /** Removes all the available desks on all displays. */
+ oneway void removeAllDesks();
+
/** Show apps on the desktop on the given display */
void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
@@ -64,8 +70,11 @@ interface IDesktopMode {
in @nullable RemoteTransition remoteTransition,
in @nullable IMoveToDesktopCallback callback);
- /** Remove desktop on the given display */
- oneway void removeDesktop(int displayId);
+ /**
+ * Removes the default desktop on the given display.
+ * @deprecated with multi-desks, we should use `removeDesk()`.
+ */
+ oneway void removeDefaultDeskInDisplay(int displayId);
/** Move a task with given `taskId` to external display */
void moveToExternalDisplay(int taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 5e84019b14f5..1a66ca808dad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -47,6 +47,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
private var boundsAnimator: Animator? = null
private var initialBounds: Rect? = null
+ private var callback: (() -> Unit)? = null
constructor(
transitions: Transitions,
@@ -61,9 +62,14 @@ class ToggleResizeDesktopTaskTransitionHandler(
* bounds of the actual task). This is provided so that the animation resizing can begin where
* the task leash currently is for smoother UX.
*/
- fun startTransition(wct: WindowContainerTransaction, taskLeashBounds: Rect? = null) {
+ fun startTransition(
+ wct: WindowContainerTransaction,
+ taskLeashBounds: Rect? = null,
+ callback: (() -> Unit)? = null,
+ ) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
initialBounds = taskLeashBounds
+ this.callback = callback
}
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
@@ -121,6 +127,8 @@ class ToggleResizeDesktopTaskTransitionHandler(
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW)
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+ callback?.invoke()
+ callback = null
},
)
addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
index 919e8164b58e..23562388b3e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
@@ -130,6 +130,12 @@ constructor(
}
}
+ /** Reparent the indicator to {@code newParent}. */
+ fun reparentLeash(t: SurfaceControl.Transaction, newParent: SurfaceControl) {
+ val leash = indicatorLeash ?: return
+ t.reparent(leash, newParent)
+ }
+
private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) {
mainExecutor.execute {
indicatorLeash = leash
@@ -166,7 +172,7 @@ constructor(
displayController.getDisplayLayout(taskInfo.displayId)
?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.")
if (currentType == IndicatorType.NO_INDICATOR) {
- fadeInIndicator(layout, newType, taskInfo.displayId, snapEventHandler)
+ fadeInIndicatorInternal(layout, newType, taskInfo.displayId, snapEventHandler)
} else if (newType == IndicatorType.NO_INDICATOR) {
fadeOutIndicator(
layout,
@@ -195,10 +201,22 @@ constructor(
}
/**
+ * Fade indicator in as provided type.
+ *
+ * Animator fades the indicator in while expanding the bounds outwards.
+ */
+ fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType, displayId: Int) {
+ if (isReleased) return
+ desktopExecutor.execute {
+ fadeInIndicatorInternal(layout, type, displayId, snapEventHandler)
+ }
+ }
+
+ /**
* Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
*/
@VisibleForTesting
- fun fadeInIndicator(
+ fun fadeInIndicatorInternal(
layout: DisplayLayout,
type: IndicatorType,
displayId: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
new file mode 100644
index 000000000000..8ce624e103ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
+import java.util.function.Supplier
+
+/**
+ * This supplies the package name of default home in an efficient way. The query to package manager
+ * only executes on initialization and when the preferred activity (e.g. default home) is changed.
+ */
+class DefaultHomePackageSupplier(
+ private val context: Context,
+ shellInit: ShellInit,
+ @ShellMainThread private val mainHandler: Handler,
+) : BroadcastReceiver(), Supplier<String?> {
+
+ private var defaultHomePackage: String? = null
+
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ private fun onInit() {
+ context.registerReceiver(
+ this,
+ IntentFilter(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED),
+ null /* broadcastPermission */,
+ mainHandler,
+ )
+ }
+
+ private fun updateDefaultHomePackage(): String? {
+ defaultHomePackage = context.packageManager.getHomeActivities(ArrayList())?.packageName
+ return defaultHomePackage
+ }
+
+ override fun onReceive(contxt: Context?, intent: Intent?) {
+ updateDefaultHomePackage()
+ }
+
+ override fun get(): String? {
+ return defaultHomePackage ?: updateDefaultHomePackage()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
index b5490cb4b595..d185ed53f78a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.desktopmode.desktopwallpaperactivity
import android.util.SparseArray
-import android.util.SparseBooleanArray
import android.view.Display.DEFAULT_DISPLAY
import android.window.WindowContainerToken
import androidx.core.util.keyIterator
@@ -28,7 +27,6 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
class DesktopWallpaperActivityTokenProvider {
private val wallpaperActivityTokenByDisplayId = SparseArray<WindowContainerToken>()
- private val wallpaperActivityVisByDisplayId = SparseBooleanArray()
fun setToken(token: WindowContainerToken, displayId: Int = DEFAULT_DISPLAY) {
logV("Setting desktop wallpaper activity token for display %s", displayId)
@@ -55,18 +53,6 @@ class DesktopWallpaperActivityTokenProvider {
}
}
- fun setWallpaperActivityIsVisible(
- isVisible: Boolean = false,
- displayId: Int = DEFAULT_DISPLAY,
- ) {
- wallpaperActivityVisByDisplayId.put(displayId, isVisible)
- }
-
- fun isWallpaperActivityVisible(displayId: Int = DEFAULT_DISPLAY): Boolean {
- return wallpaperActivityTokenByDisplayId[displayId] != null &&
- wallpaperActivityVisByDisplayId.get(displayId, false)
- }
-
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
index fc359d7d67b6..5a988fcd1b77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
@@ -18,6 +18,8 @@ package com.android.wm.shell.desktopmode.multidesks
import android.app.ActivityManager
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
+import kotlin.coroutines.suspendCoroutine
/** An organizer of desk containers in which to host child desktop windows. */
interface DesksOrganizer {
@@ -40,6 +42,13 @@ interface DesksOrganizer {
task: ActivityManager.RunningTaskInfo,
)
+ /** Reorders a desk's task to the front. */
+ fun reorderTaskToFront(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: ActivityManager.RunningTaskInfo,
+ )
+
/** Minimizes the given task of the given deskId. */
fun minimizeTask(
wct: WindowContainerTransaction,
@@ -47,6 +56,13 @@ interface DesksOrganizer {
task: ActivityManager.RunningTaskInfo,
)
+ /** Unminimize the given task of the given desk. */
+ fun unminimizeTask(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: ActivityManager.RunningTaskInfo,
+ )
+
/** Whether the change is for the given desk id. */
fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean
@@ -68,3 +84,9 @@ interface DesksOrganizer {
fun onCreated(deskId: Int)
}
}
+
+/** Creates a new desk container in the given display. */
+suspend fun DesksOrganizer.createDesk(displayId: Int): Int = suspendCoroutine { cont ->
+ val onCreateCallback = OnCreateCallback { deskId -> cont.resumeWith(Result.success(deskId)) }
+ createDesk(displayId, onCreateCallback)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
index f576258ebdaa..49ca58e7b32a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
@@ -33,6 +33,7 @@ import androidx.core.util.forEach
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
@@ -44,6 +45,7 @@ class RootTaskDesksOrganizer(
shellInit: ShellInit,
shellCommandHandler: ShellCommandHandler,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val launchAdjacentController: LaunchAdjacentController,
) : DesksOrganizer, ShellTaskOrganizer.TaskListener {
private val createDeskRootRequests = mutableListOf<CreateDeskRequest>()
@@ -110,7 +112,31 @@ class RootTaskDesksOrganizer(
wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
}
+ override fun reorderTaskToFront(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: RunningTaskInfo,
+ ) {
+ logV("reorderTaskToFront task=${task.taskId} desk=$deskId")
+ val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId")
+ if (task.taskId in root.children) {
+ wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true)
+ return
+ }
+ val minimizationRoot =
+ checkNotNull(deskMinimizationRootsByDeskId[deskId]) {
+ "Minimization root not found for desk: $deskId"
+ }
+ if (task.taskId in minimizationRoot.children) {
+ unminimizeTask(wct, deskId, task)
+ wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true)
+ return
+ }
+ logE("Attempted to reorder task=${task.taskId} in desk=$deskId but it was not a child")
+ }
+
override fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo) {
+ logV("minimizeTask task=${task.taskId} desk=$deskId")
val deskRoot =
checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
val minimizationRoot =
@@ -129,6 +155,30 @@ class RootTaskDesksOrganizer(
wct.reparent(task.token, minimizationRoot.token, /* onTop= */ true)
}
+ override fun unminimizeTask(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: RunningTaskInfo,
+ ) {
+ val taskId = task.taskId
+ logV("unminimizeTask task=$taskId desk=$deskId")
+ val deskRoot =
+ checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
+ val minimizationRoot =
+ checkNotNull(deskMinimizationRootsByDeskId[deskId]) {
+ "Minimization root not found for desk: $deskId"
+ }
+ if (taskId in deskRoot.children) {
+ logV("Task #$taskId is already unminimized in desk=$deskId")
+ return
+ }
+ if (taskId !in minimizationRoot.children) {
+ logE("Attempted to unminimize task=$taskId in desk=$deskId but it was not a child")
+ return
+ }
+ wct.reparent(task.token, deskRoot.token, /* onTop= */ true)
+ }
+
override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean =
(isDeskRootChange(change) && change.taskId == deskId) ||
(getDeskMinimizationRootInChange(change)?.deskId == deskId)
@@ -164,6 +214,21 @@ class RootTaskDesksOrganizer(
change.mode == TRANSIT_TO_FRONT
override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
+ handleTaskAppeared(taskInfo, leash)
+ updateLaunchAdjacentController()
+ }
+
+ override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
+ handleTaskInfoChanged(taskInfo)
+ updateLaunchAdjacentController()
+ }
+
+ override fun onTaskVanished(taskInfo: RunningTaskInfo) {
+ handleTaskVanished(taskInfo)
+ updateLaunchAdjacentController()
+ }
+
+ private fun handleTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
// Check whether this task is appearing inside a desk.
if (taskInfo.parentTaskId in deskRootsByDeskId) {
val deskId = taskInfo.parentTaskId
@@ -216,7 +281,7 @@ class RootTaskDesksOrganizer(
hideMinimizationRoot(deskMinimizationRoot)
}
- override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
+ private fun handleTaskInfoChanged(taskInfo: RunningTaskInfo) {
if (deskRootsByDeskId.contains(taskInfo.taskId)) {
val deskId = taskInfo.taskId
deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo)
@@ -254,7 +319,7 @@ class RootTaskDesksOrganizer(
logE("onTaskInfoChanged: unknown task: ${taskInfo.taskId}")
}
- override fun onTaskVanished(taskInfo: RunningTaskInfo) {
+ private fun handleTaskVanished(taskInfo: RunningTaskInfo) {
if (deskRootsByDeskId.contains(taskInfo.taskId)) {
val deskId = taskInfo.taskId
val deskRoot = deskRootsByDeskId[deskId]
@@ -336,6 +401,18 @@ class RootTaskDesksOrganizer(
deskRootsByDeskId.forEach { _, deskRoot -> deskRoot.children -= taskId }
}
+ private fun updateLaunchAdjacentController() {
+ deskRootsByDeskId.forEach { deskId, root ->
+ if (root.taskInfo.isVisible) {
+ // Disable launch adjacent handling if any desk is active, otherwise the split
+ // launch root and the desk root will both be eligible to take launching tasks.
+ launchAdjacentController.launchAdjacentEnabled = false
+ return
+ }
+ }
+ launchAdjacentController.launchAdjacentEnabled = true
+ }
+
@VisibleForTesting
data class DeskRoot(
val deskId: Int,
@@ -377,6 +454,9 @@ class RootTaskDesksOrganizer(
override fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("$prefix$TAG")
+ pw.println(
+ "${innerPrefix}launchAdjacentEnabled=" + launchAdjacentController.launchAdjacentEnabled
+ )
pw.println("${innerPrefix}Desk Roots:")
deskRootsByDeskId.forEach { deskId, root ->
val minimizationRoot = deskMinimizationRootsByDeskId[deskId]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
index 1566544f5303..f71eacab518d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -132,10 +132,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis
.toBuilder()
.putDesktopRepoByUser(
userId,
- currentRepository
- .toBuilder()
- .putDesktop(desktopId, desktop.build())
- .build(),
+ currentRepository.toBuilder().putDesktop(desktopId, desktop.build()).build(),
)
.build()
}
@@ -149,6 +146,33 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis
}
}
+ /** Removes the desktop from the persistent repository. */
+ suspend fun removeDesktop(userId: Int, desktopId: Int) {
+ try {
+ dataStore.updateData { persistentRepositories: DesktopPersistentRepositories ->
+ val currentRepository =
+ persistentRepositories.getDesktopRepoByUserOrDefault(
+ userId,
+ DesktopRepositoryState.getDefaultInstance(),
+ )
+ persistentRepositories
+ .toBuilder()
+ .putDesktopRepoByUser(
+ userId,
+ currentRepository.toBuilder().removeDesktop(desktopId).build(),
+ )
+ .build()
+ }
+ } catch (throwable: Throwable) {
+ Log.e(
+ TAG,
+ "Error in removing desktop related data, data is " +
+ "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
+ throwable,
+ )
+ }
+ }
+
suspend fun removeUsers(uids: List<Int>) {
try {
dataStore.updateData { persistentRepositories: DesktopPersistentRepositories ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
index a26ebbf4c99a..8191181cac11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
@@ -17,8 +17,22 @@
package com.android.wm.shell.desktopmode.persistence
import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import kotlinx.coroutines.flow.StateFlow
/** Interface for initializing the [DesktopUserRepositories]. */
-fun interface DesktopRepositoryInitializer {
+interface DesktopRepositoryInitializer {
+ /** A factory used to recreate a desk from persistence. */
+ var deskRecreationFactory: DeskRecreationFactory
+
+ /** A flow that emits true when the repository has been initialized. */
+ val isInitialized: StateFlow<Boolean>
+
+ /** Initialize the user repositories from a persistent data store. */
fun initialize(userRepositories: DesktopUserRepositories)
+
+ /** A factory for recreating desks. */
+ fun interface DeskRecreationFactory {
+ /** Recreates a restored desk and returns the new desk id. */
+ suspend fun recreateDesk(userId: Int, destinationDisplayId: Int, deskId: Int): Int
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
index 0507e59c06e1..49cb7391fe97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -17,13 +17,19 @@
package com.android.wm.shell.desktopmode.persistence
import android.content.Context
+import android.view.Display
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
/**
@@ -37,62 +43,136 @@ class DesktopRepositoryInitializerImpl(
private val persistentRepository: DesktopPersistentRepository,
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
) : DesktopRepositoryInitializer {
+
+ override var deskRecreationFactory: DeskRecreationFactory = DefaultDeskRecreationFactory()
+
+ private val _isInitialized = MutableStateFlow(false)
+ override val isInitialized: StateFlow<Boolean> = _isInitialized
+
override fun initialize(userRepositories: DesktopUserRepositories) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) {
+ _isInitialized.value = true
+ return
+ }
// TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
mainCoroutineScope.launch {
- val desktopUserPersistentRepositoryMap =
- persistentRepository.getUserDesktopRepositoryMap() ?: return@launch
- for (userId in desktopUserPersistentRepositoryMap.keys) {
- val repository = userRepositories.getProfile(userId)
- val desktopRepositoryState =
- persistentRepository.getDesktopRepositoryState(userId) ?: continue
- val desktopByDesktopIdMap = desktopRepositoryState.desktopMap
- for (desktopId in desktopByDesktopIdMap.keys) {
- val persistentDesktop =
- persistentRepository.readDesktop(userId, desktopId) ?: continue
- val maxTasks =
- DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
- ?: persistentDesktop.zOrderedTasksCount
- var visibleTasksCount = 0
- repository.addDesk(
- displayId = persistentDesktop.displayId,
- deskId =
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- persistentDesktop.desktopId
- } else {
- // When disabled, desk ids are always the display id.
- persistentDesktop.displayId
- },
+ try {
+ val desktopUserPersistentRepositoryMap =
+ persistentRepository.getUserDesktopRepositoryMap() ?: return@launch
+ for (userId in desktopUserPersistentRepositoryMap.keys) {
+ val repository = userRepositories.getProfile(userId)
+ val desktopRepositoryState =
+ persistentRepository.getDesktopRepositoryState(userId) ?: continue
+ val desksToRestore = getDesksToRestore(desktopRepositoryState, userId)
+ logV(
+ "initialize() will restore desks=%s user=%d",
+ desksToRestore.map { it.desktopId },
+ userId,
)
- persistentDesktop.zOrderedTasksList
- // Reverse it so we initialize the repo from bottom to top.
- .reversed()
- .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] }
- // TODO: b/362720497 - add tasks to their respective desk when multi-desk
- // persistence is implemented.
- .forEach { task ->
- if (
- task.desktopTaskState == DesktopTaskState.VISIBLE &&
- visibleTasksCount < maxTasks
- ) {
- visibleTasksCount++
- repository.addTask(
- persistentDesktop.displayId,
- task.taskId,
- isVisible = false,
- )
- } else {
- repository.addTask(
- persistentDesktop.displayId,
- task.taskId,
+ desksToRestore.forEach { persistentDesktop ->
+ val maxTasks = getTaskLimit(persistentDesktop)
+ val displayId = persistentDesktop.displayId
+ val deskId = persistentDesktop.desktopId
+ // TODO: b/401107440 - Implement desk restoration to other displays.
+ val newDisplayId = Display.DEFAULT_DISPLAY
+ val newDeskId =
+ deskRecreationFactory.recreateDesk(
+ userId = userId,
+ destinationDisplayId = newDisplayId,
+ deskId = deskId,
+ )
+ logV(
+ "Recreated desk=%d in display=%d using new deskId=%d and displayId=%d",
+ deskId,
+ displayId,
+ newDeskId,
+ newDisplayId,
+ )
+ if (newDeskId != deskId || newDisplayId != displayId) {
+ logV("Removing obsolete desk from persistence under deskId=%d", deskId)
+ persistentRepository.removeDesktop(userId, deskId)
+ }
+
+ // TODO: b/393961770 - [DesktopRepository] doesn't save desks to the
+ // persistent repository until a task is added to them. Update it so that
+ // empty desks can be restored too.
+ repository.addDesk(displayId = displayId, deskId = newDeskId)
+ var visibleTasksCount = 0
+ persistentDesktop.zOrderedTasksList
+ // Reverse it so we initialize the repo from bottom to top.
+ .reversed()
+ .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] }
+ .forEach { task ->
+ // Visible here means non-minimized a.k.a. expanded, it does not
+ // mean
+ // it is visible in WM (and |DesktopRepository|) terms.
+ val isVisible =
+ task.desktopTaskState == DesktopTaskState.VISIBLE &&
+ visibleTasksCount < maxTasks
+
+ repository.addTaskToDesk(
+ displayId = displayId,
+ deskId = newDeskId,
+ taskId = task.taskId,
isVisible = false,
)
- repository.minimizeTask(persistentDesktop.displayId, task.taskId)
+
+ if (isVisible) {
+ visibleTasksCount++
+ } else {
+ repository.minimizeTaskInDesk(
+ displayId = displayId,
+ deskId = newDeskId,
+ taskId = task.taskId,
+ )
+ }
}
- }
+ }
}
+ } finally {
+ _isInitialized.value = true
}
}
}
+
+ private suspend fun getDesksToRestore(
+ state: DesktopRepositoryState,
+ userId: Int,
+ ): Set<Desktop> {
+ // TODO: b/365873835 - what about desks that won't be restored?
+ // - invalid desk ids from multi-desk -> single-desk switching can be ignored / deleted.
+ val limitToSingleDeskPerDisplay =
+ !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
+ return state.desktopMap.keys
+ .mapNotNull { deskId ->
+ persistentRepository.readDesktop(userId, deskId)?.takeIf { desk ->
+ // Do not restore invalid desks when multi-desks is disabled. This is
+ // possible if the feature is disabled after having created multiple desks.
+ val isValidSingleDesk = desk.desktopId == desk.displayId
+ (!limitToSingleDeskPerDisplay || isValidSingleDesk)
+ }
+ }
+ .toSet()
+ }
+
+ private fun getTaskLimit(persistedDesk: Desktop): Int =
+ DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
+ ?: persistedDesk.zOrderedTasksCount
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ /** A default implementation of [DeskRecreationFactory] that reuses the desk id. */
+ private class DefaultDeskRecreationFactory : DeskRecreationFactory {
+ override suspend fun recreateDesk(
+ userId: Int,
+ destinationDisplayId: Int,
+ deskId: Int,
+ ): Int = deskId
+ }
+
+ companion object {
+ private const val TAG = "DesktopRepositoryInitializerImpl"
+ }
}
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 897e2d1601a5..2fe786531af5 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
@@ -24,6 +24,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import com.android.internal.protolog.ProtoLog;
@@ -167,6 +168,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
}
private void updateLaunchAdjacentController() {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+ // With multiple desks, freeform tasks are children of a root task controlled by
+ // DesksOrganizer, so toggling launch-adjacent should be managed there.
+ return;
+ }
for (int i = 0; i < mTasks.size(); i++) {
if (mTasks.valueAt(i).mTaskInfo.isVisible) {
mLaunchAdjacentController.setLaunchAdjacentEnabled(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 8059b94685ba..0bf2ea61b0a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
@@ -52,6 +53,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
private final WindowDecorViewModel mWindowDecorViewModel;
private final Optional<TaskChangeListener> mTaskChangeListener;
private final FocusTransitionObserver mFocusTransitionObserver;
+ private final Optional<DesksTransitionObserver> mDesksTransitionObserver;
private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
new HashMap<>();
@@ -63,12 +65,14 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
Optional<DesktopImmersiveController> desktopImmersiveController,
WindowDecorViewModel windowDecorViewModel,
Optional<TaskChangeListener> taskChangeListener,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ Optional<DesksTransitionObserver> desksTransitionObserver) {
mTransitions = transitions;
mDesktopImmersiveController = desktopImmersiveController;
mWindowDecorViewModel = windowDecorViewModel;
mTaskChangeListener = taskChangeListener;
mFocusTransitionObserver = focusTransitionObserver;
+ mDesksTransitionObserver = desksTransitionObserver;
if (FreeformComponents.requiresFreeformComponents(context)) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -85,6 +89,10 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
+ // Update desk state first, otherwise [TaskChangeListener] may update desktop task state
+ // under an outdated active desk if a desk switch and a task update happen in the same
+ // transition, such as when unminimizing a task from an inactive desk.
+ mDesksTransitionObserver.ifPresent(o -> o.onTransitionReady(transition, info));
if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
// TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository
// is updated from there **before** the |mWindowDecorViewModel| methods are invoked.
@@ -166,7 +174,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
SurfaceControl.Transaction finishT) {
mTaskChangeListener.ifPresent(listener -> listener.onTaskChanging(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
}
private void onToFrontTransitionReady(
@@ -176,7 +184,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
mTaskChangeListener.ifPresent(
listener -> listener.onTaskMovingToFront(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
}
private void onToBackTransitionReady(
@@ -186,7 +194,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
mTaskChangeListener.ifPresent(
listener -> listener.onTaskMovingToBack(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 04f03361258e..0966110d2f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -70,6 +70,7 @@ import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.DesktopModeFlags;
import android.window.DisplayAreaInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
@@ -78,7 +79,6 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -781,7 +781,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// TODO(b/377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
private boolean isPipExitingToDesktopMode() {
DesktopRepository currentRepo = getCurrentRepo();
- return Flags.enableDesktopWindowingPip() && currentRepo != null
+ return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue() && currentRepo != null
&& (currentRepo.isAnyDeskActive(mTaskInfo.displayId)
|| isDisplayInFreeform());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 242f7fa5f1b1..5706f19f177a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -39,11 +39,11 @@ import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.window.DesktopModeFlags;
import androidx.annotation.VisibleForTesting;
import com.android.internal.policy.TaskResizingAlgorithm;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -183,7 +183,7 @@ public class PipResizeGestureHandler {
private void reloadResources() {
final Resources res = mContext.getResources();
mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
- mEnableDragCornerResize = Flags.enableDesktopWindowingPip();
+ mEnableDragCornerResize = DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue();
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
index 65099c2dfb9d..671eae3d84ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -153,7 +153,12 @@ public class PhonePipMenuController implements PipMenuController,
mPipUiEventLogger = pipUiEventLogger;
mPipTransitionState.addPipTransitionStateChangedListener(this);
-
+ // Clear actions after exit PiP. Otherwise, next PiP could accidentally inherit the
+ // actions provided by the previous app in PiP mode.
+ mPipBoundsState.addOnPipComponentChangedListener(((oldPipComponent, newPipComponent) -> {
+ if (mAppActions != null) mAppActions.clear();
+ mCloseAction = null;
+ }));
mPipTaskListener.addParamsChangedListener(new PipTaskListener.PipParamsChangedCallback() {
@Override
public void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 6012fe66188d..119763ff2022 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -238,7 +238,10 @@ public class PipController implements ConfigurationChangeListener,
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onActivityRestartAttempt: topActivity=%s, wasVisible=%b",
+ task.topActivity, wasVisible);
+ if (task.getWindowingMode() != WINDOWING_MODE_PINNED || !wasVisible) {
return;
}
mPipScheduler.scheduleExitPipViaExpand();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java
new file mode 100644
index 000000000000..321952480094
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pip2.phone;
+
+import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Helps track PIP CUJ interactions
+ */
+public class PipInteractionHandler {
+ @IntDef(prefix = {"INTERACTION_"}, value = {
+ INTERACTION_EXIT_PIP,
+ INTERACTION_EXIT_PIP_TO_SPLIT
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Interaction {}
+
+ public static final int INTERACTION_EXIT_PIP = 0;
+ public static final int INTERACTION_EXIT_PIP_TO_SPLIT = 1;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+
+ public PipInteractionHandler(Context context, Handler handler,
+ InteractionJankMonitor interactionJankMonitor) {
+ mContext = context;
+ mHandler = handler;
+ mInteractionJankMonitor = interactionJankMonitor;
+ }
+
+ /**
+ * Begin tracking PIP CUJ.
+ *
+ * @param leash PIP leash.
+ * @param interaction Tag for interaction.
+ */
+ public void begin(SurfaceControl leash, @Interaction int interaction) {
+ mInteractionJankMonitor.begin(leash, mContext, mHandler, CUJ_PIP_TRANSITION,
+ pipInteractionToString(interaction));
+ }
+
+ /**
+ * End tracking CUJ.
+ */
+ public void end() {
+ mInteractionJankMonitor.end(CUJ_PIP_TRANSITION);
+ }
+
+ /**
+ * Converts an interaction to a string representation used for tagging.
+ *
+ * @param interaction Interaction to track.
+ * @return String representation of the interaction.
+ */
+ public static String pipInteractionToString(@Interaction int interaction) {
+ return switch (interaction) {
+ case INTERACTION_EXIT_PIP -> "EXIT_PIP";
+ case INTERACTION_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+ default -> "";
+ };
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 383afcf6f821..a02a51f92b1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -20,6 +20,7 @@ import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
@@ -47,7 +48,7 @@ import java.util.function.Supplier;
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
-public class PipScheduler {
+public class PipScheduler implements PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = PipScheduler.class.getSimpleName();
/**
@@ -71,6 +72,7 @@ public class PipScheduler {
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
@Nullable private Runnable mUpdateMovementBoundsRunnable;
+ @Nullable private PipAlphaAnimator mOverlayFadeoutAnimator;
private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
private Supplier<PictureInPictureParams> mPipParamsSupplier;
@@ -85,6 +87,7 @@ public class PipScheduler {
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
mPipDesktopState = pipDesktopState;
mSplitScreenControllerOptional = splitScreenControllerOptional;
@@ -177,10 +180,17 @@ public class PipScheduler {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(pipTaskToken, toBounds);
if (configAtEnd) {
wct.deferConfigToTransitionEnd(pipTaskToken);
+
+ if (mPipBoundsState.getBounds().width() == toBounds.width()
+ && mPipBoundsState.getBounds().height() == toBounds.height()) {
+ // TODO (b/393159816): Config-at-End causes a flicker without size change.
+ // If PiP size isn't changing enforce a minimal one-pixel change as a workaround.
+ --toBounds.bottom;
+ }
}
+ wct.setBounds(pipTaskToken, toBounds);
mPipTransitionController.startResizeTransition(wct, duration);
}
@@ -238,12 +248,16 @@ public class PipScheduler {
void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
boolean withStartDelay, @NonNull Runnable onAnimationEnd) {
- PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext, overlayLeash,
+ mOverlayFadeoutAnimator = mPipAlphaAnimatorSupplier.get(mContext, overlayLeash,
null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
- animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS);
- animator.setStartDelay(withStartDelay ? EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
- animator.setAnimationEndCallback(onAnimationEnd);
- animator.start();
+ mOverlayFadeoutAnimator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS);
+ mOverlayFadeoutAnimator.setStartDelay(withStartDelay
+ ? EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
+ mOverlayFadeoutAnimator.setAnimationEndCallback(() -> {
+ onAnimationEnd.run();
+ mOverlayFadeoutAnimator = null;
+ });
+ mOverlayFadeoutAnimator.start();
}
void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
@@ -289,6 +303,21 @@ public class PipScheduler {
mSurfaceControlTransactionFactory = factory;
}
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState,
+ @android.annotation.Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.EXITING_PIP:
+ case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+ if (mOverlayFadeoutAnimator != null && mOverlayFadeoutAnimator.isStarted()) {
+ mOverlayFadeoutAnimator.end();
+ mOverlayFadeoutAnimator = null;
+ }
+ break;
+ }
+ }
+
@VisibleForTesting
interface PipAlphaAnimatorSupplier {
PipAlphaAnimator get(@NonNull Context context,
@@ -303,6 +332,17 @@ public class PipScheduler {
mPipAlphaAnimatorSupplier = supplier;
}
+ @VisibleForTesting
+ void setOverlayFadeoutAnimator(@NonNull PipAlphaAnimator animator) {
+ mOverlayFadeoutAnimator = animator;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ PipAlphaAnimator getOverlayFadeoutAnimator() {
+ return mOverlayFadeoutAnimator;
+ }
+
void setPipParamsSupplier(@NonNull Supplier<PictureInPictureParams> pipParamsSupplier) {
mPipParamsSupplier = pipParamsSupplier;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index d6634845ee21..294ef48c01d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -61,7 +61,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
private final PipBoundsState mPipBoundsState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final ShellExecutor mMainExecutor;
- private final PictureInPictureParams mPictureInPictureParams =
+ private PictureInPictureParams mPictureInPictureParams =
new PictureInPictureParams.Builder().build();
private boolean mWaitingForAspectRatioChange = false;
@@ -92,6 +92,11 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
}
mPipResizeAnimatorSupplier = PipResizeAnimator::new;
mPipScheduler.setPipParamsSupplier(this::getPictureInPictureParams);
+ // Reset {@link #mPictureInPictureParams} after exiting PiP. For instance, next Activity
+ // with null aspect ratio would accidentally inherit the aspect ratio from a previous
+ // PiP Activity.
+ mPipBoundsState.addOnPipComponentChangedListener(((oldPipComponent, newPipComponent) ->
+ mPictureInPictureParams = new PictureInPictureParams.Builder().build()));
}
void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
@@ -138,9 +143,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
if (mPictureInPictureParams.hasSetAspectRatio()
&& mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(newAspectRatio)
&& PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
- mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
- onAspectRatioChanged(newAspectRatio);
- });
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(
+ () -> onAspectRatioChanged(newAspectRatio));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 6fdfecaf15d5..d1bc450c3c4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -934,6 +934,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
}
// the size to toggle to after a double tap
+ mPipBoundsState.setNormalBounds(getAdjustedNormalBounds());
int nextSize = PipDoubleTapHelper
.nextSizeSpec(mPipBoundsState, getUserResizeBounds());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 91fbd456eb63..9bb2e38e1526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -113,6 +113,7 @@ public class PipTransition extends PipTransitionController implements
private final DisplayController mDisplayController;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
private final PipDesktopState mPipDesktopState;
+ private final PipInteractionHandler mPipInteractionHandler;
//
// Transition caches
@@ -154,7 +155,8 @@ public class PipTransition extends PipTransitionController implements
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
Optional<SplitScreenController> splitScreenControllerOptional,
- PipDesktopState pipDesktopState) {
+ PipDesktopState pipDesktopState,
+ PipInteractionHandler pipInteractionHandler) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -168,9 +170,11 @@ public class PipTransition extends PipTransitionController implements
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
mPipDesktopState = pipDesktopState;
+ mPipInteractionHandler = pipInteractionHandler;
mExpandHandler = new PipExpandHandler(mContext, pipBoundsState, pipBoundsAlgorithm,
- pipTransitionState, pipDisplayLayoutState, splitScreenControllerOptional);
+ pipTransitionState, pipDisplayLayoutState, pipInteractionHandler,
+ splitScreenControllerOptional);
}
@Override
@@ -770,7 +774,7 @@ public class PipTransition extends PipTransitionController implements
// Since opening a new task while in Desktop Mode always first open in Fullscreen
// until DesktopMode Shell code resolves it to Freeform, PipTransition will get a
// possibility to handle it also. In this case return false to not have it enter PiP.
- if (mPipDesktopState.isPipEnteringInDesktopMode(pipTask)) {
+ if (mPipDesktopState.isPipInDesktopMode()) {
return false;
}
@@ -794,9 +798,26 @@ public class PipTransition extends PipTransitionController implements
setEnterAnimationType(ANIM_TYPE_BOUNDS);
return true;
}
- // If the only change in the changes list is a opening type PiP task,
+
+ // Sometimes root PiP task can have TF children. These child containers can be collected
+ // even if they can promote to their parents: e.g. if they are marked as "organized".
+ // So we count the chain of containers under PiP task as one "real" changing target;
+ // iterate through changes bottom-to-top to properly identify parents.
+ int expectedTargetCount = 1;
+ WindowContainerToken lastPipChildToken = pipChange.getContainer();
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange || change.getContainer() == null) continue;
+ if (change.getParent() != null && change.getParent().equals(lastPipChildToken)) {
+ // Allow an extra change since our pinned root task has a child.
+ ++expectedTargetCount;
+ lastPipChildToken = change.getContainer();
+ }
+ }
+
+ // If the only root task change in the changes list is a opening type PiP task,
// then this is legacy-enter PiP.
- return info.getChanges().size() == 1
+ return info.getChanges().size() == expectedTargetCount
&& TransitionUtil.isOpeningMode(pipChange.getMode());
}
return false;
@@ -930,14 +951,6 @@ public class PipTransition extends PipTransitionController implements
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- if (mPipDesktopState.shouldExitPipExitDesktopMode()) {
- mTransitions.startTransition(
- TRANSIT_TO_BACK,
- mPipDesktopState.getWallpaperActivityTokenWct(
- mPipTransitionState.getPipTaskInfo().getDisplayId()),
- null /* firstHandler */
- );
- }
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
mPendingRemoveWithFadeout = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 18c9a705dcf7..9e2fbfe2e282 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -26,9 +26,11 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -123,6 +125,8 @@ public class PipTransitionState {
@ShellMainThread
private final Handler mMainHandler;
+ private final PipDesktopState mPipDesktopState;
+
//
// Swipe up to enter PiP related state
//
@@ -172,8 +176,9 @@ public class PipTransitionState {
private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
- public PipTransitionState(@ShellMainThread Handler handler) {
+ public PipTransitionState(@ShellMainThread Handler handler, PipDesktopState pipDesktopState) {
mMainHandler = handler;
+ mPipDesktopState = pipDesktopState;
}
/**
@@ -384,12 +389,16 @@ public class PipTransitionState {
return ++mPrevCustomState;
}
- private boolean shouldTransitionToState(@TransitionState int newState) {
+ @VisibleForTesting
+ boolean shouldTransitionToState(@TransitionState int newState) {
switch (newState) {
case SCHEDULED_BOUNDS_CHANGE:
- // Allow scheduling bounds change only while in PiP, except for if another bounds
- // change was scheduled but hasn't started playing yet.
- return isInPip();
+ // Allow scheduling bounds change only when both of these are true:
+ // - while in PiP, except for if another bounds change was scheduled but hasn't
+ // started playing yet
+ // - there is no drag-to-desktop gesture in progress; otherwise the PiP resize
+ // transition will block the drag-to-desktop transitions from finishing
+ return isInPip() && !mPipDesktopState.isDragToDesktopInProgress();
default:
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
index db4942b2fb95..3274f4ae354a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
@@ -45,6 +45,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -58,6 +59,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipInteractionHandler mPipInteractionHandler;
private final Optional<SplitScreenController> mSplitScreenControllerOptional;
@Nullable
@@ -72,12 +74,14 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTransitionState pipTransitionState,
PipDisplayLayoutState pipDisplayLayoutState,
+ PipInteractionHandler pipInteractionHandler,
Optional<SplitScreenController> splitScreenControllerOptional) {
mContext = context;
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipTransitionState = pipTransitionState;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipInteractionHandler = pipInteractionHandler;
mSplitScreenControllerOptional = splitScreenControllerOptional;
mPipExpandAnimatorSupplier = PipExpandAnimator::new;
@@ -183,6 +187,8 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
sourceRectHint, delta);
+ animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
+ PipInteractionHandler.INTERACTION_EXIT_PIP));
animator.setAnimationEndCallback(() -> {
if (parentBeforePip != null) {
// TODO b/377362511: Animate local leash instead to also handle letterbox case.
@@ -190,6 +196,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
finishTransaction.setCrop(pipLeash, null);
}
finishTransition();
+ mPipInteractionHandler.end();
});
cacheAndStartTransitionAnimator(animator);
saveReentryState();
@@ -248,6 +255,8 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
splitController.finishEnterSplitScreen(finishTransaction);
});
+ animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
+ PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
animator.setAnimationEndCallback(() -> {
if (parentBeforePip == null) {
// After PipExpandAnimator is done modifying finishTransaction, we need to make
@@ -256,6 +265,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
finishTransaction.setPosition(pipLeash, 0, 0);
}
finishTransition();
+ mPipInteractionHandler.end();
});
cacheAndStartTransitionAnimator(animator);
saveReentryState();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index fed336b17f19..65fa9b4b5529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -52,6 +52,7 @@ import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
@@ -362,7 +363,8 @@ class SplitScreenTransitions {
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- int extraTransitType, boolean resizeAnim) {
+ int extraTransitType, boolean resizeAnim,
+ @SplitScreenConstants.PersistentSnapPosition int snapPosition) {
if (mPendingEnter != null) {
ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start enter split transition since it already exist. ");
@@ -373,16 +375,18 @@ class SplitScreenTransitions {
.onSplitAnimationInvoked(true /*animationRunning*/));
}
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
+ setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim,
+ snapPosition);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
@Nullable RemoteTransition remoteTransition,
- int extraTransitType, boolean resizeAnim) {
+ int extraTransitType, boolean resizeAnim,
+ int snapPosition) {
mPendingEnter = new EnterSession(
- transition, remoteTransition, extraTransitType, resizeAnim);
+ transition, remoteTransition, extraTransitType, resizeAnim, snapPosition);
ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -675,13 +679,16 @@ class SplitScreenTransitions {
/** Bundled information of enter transition. */
class EnterSession extends TransitSession {
final boolean mResizeAnim;
+ /** The starting snap position we'll enter into with this transition. */
+ final @SplitScreenConstants.PersistentSnapPosition int mEnteringPosition;
EnterSession(IBinder transition,
@Nullable RemoteTransition remoteTransition,
- int extraTransitType, boolean resizeAnim) {
+ int extraTransitType, boolean resizeAnim, int snapPosition) {
super(transition, null /* consumedCallback */, null /* finishedCallback */,
remoteTransition, extraTransitType);
this.mResizeAnim = resizeAnim;
+ this.mEnteringPosition = snapPosition;
}
}
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 a3a808de6ff1..a7cba76ea91f 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
@@ -93,7 +93,6 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -124,6 +123,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.DesktopExperienceFlags;
+import android.window.DesktopModeFlags;
import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -653,7 +653,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
null, this,
isSplitScreenVisible()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
- !mIsDropEntering);
+ !mIsDropEntering, SNAP_TO_2_50_50);
// Due to drag already pip task entering split by this method so need to reset flag here.
mIsDropEntering = false;
@@ -676,7 +676,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!enteredSplitSelect) {
return null;
}
- if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
+ && !DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
mTaskOrganizer.applyTransaction(wct);
return null;
}
@@ -787,7 +788,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
- extraTransitType, !mIsDropEntering);
+ extraTransitType, !mIsDropEntering, SNAP_TO_2_50_50);
}
/**
@@ -833,7 +834,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
- extraTransitType, !mIsDropEntering);
+ extraTransitType, !mIsDropEntering, SNAP_TO_2_50_50);
}
/**
@@ -848,6 +849,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
"startTasks: task1=%d task2=%d position=%d snapPosition=%d",
taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ // If the two tasks are already in split screen on external display, only reparent the
+ // split root to the default display if the app pair is clicked on default display.
+ // TODO(b/393217881): cover more cases and extract this to a new method when split screen
+ // in connected display is fully supported.
+ if (enableNonDefaultDisplaySplit()) {
+ DisplayAreaInfo displayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY);
+ ActivityManager.RunningTaskInfo taskInfo1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
+ ActivityManager.RunningTaskInfo taskInfo2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
+
+ if (displayAreaInfo != null && taskInfo1 != null && taskInfo2 != null
+ && getStageOfTask(taskId1) != STAGE_TYPE_UNDEFINED
+ && getStageOfTask(taskId2) != STAGE_TYPE_UNDEFINED
+ && taskInfo1.displayId != DEFAULT_DISPLAY
+ && taskInfo1.displayId == taskInfo2.displayId) {
+ wct.reparent(mRootTaskInfo.token, displayAreaInfo.token, true);
+ mTaskOrganizer.applyTransaction(wct);
+ return;
+ }
+ }
+
if (taskId2 == INVALID_TASK_ID) {
startSingleTask(taskId1, options1, wct, remoteTransition);
return;
@@ -1029,7 +1051,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mPausingTasks.clear();
}
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, snapPosition);
setEnterInstanceId(instanceId);
}
@@ -1119,7 +1141,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, snapPosition);
setEnterInstanceId(instanceId);
}
@@ -1624,6 +1646,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
grantFocusToStage(stageToFocus);
}
+ private void grantFocusForSnapPosition(@PersistentSnapPosition int enteringPosition) {
+ switch (enteringPosition) {
+ case SNAP_TO_2_90_10 -> grantFocusToPosition(true /*leftOrTop*/);
+ case SNAP_TO_2_10_90 -> grantFocusToPosition(false /*leftOrTop*/);
+ default -> { /*no-op*/ }
+ }
+ }
+
private void clearRequestIfPresented() {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
if (mSideStage.mVisible && mSideStage.mHasChildren
@@ -2220,10 +2250,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setRootForceTranslucent(true, wct);
if (!enableFlexibleSplit()) {
- //TODO(b/373709676) Need to figure out how adjacentRoots work for flex split
+ // TODO: consider support 3 splits
// Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ wct.setAdjacentRootSet(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
}
@@ -2234,7 +2264,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
} else {
- // TODO(b/373709676) Need to figure out how adjacentRoots work for flex split
+ // TODO: consider support 3 splits
}
}
@@ -2890,8 +2920,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// split, prepare to enter split screen.
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
- } else if (isSplitScreenVisible() && isOpening) {
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50);
+ } else if (enableFlexibleTwoAppSplit() && isSplitScreenVisible() && isOpening) {
// launching into an existing split stage; possibly launchAdjacent
// If we're replacing a pip-able app, we need to let mixed handler take care of
// it. Otherwise we'll just treat it as an enter+resize
@@ -2899,7 +2929,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// updated layout will get applied in startAnimation pendingResize
mSplitTransitions.setEnterTransition(transition,
request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/);
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/,
+ SNAP_TO_2_50_50);
}
} else if (inFullscreen && isSplitScreenVisible()) {
// If the trigger task is in fullscreen and in split, exit split and place
@@ -2977,14 +3008,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50);
return out;
}
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
+ "restoring to split", request.getDebugId());
out = new WindowContainerTransaction();
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */,
+ SNAP_TO_2_50_50);
}
return out;
}
@@ -3171,7 +3203,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (keepSplitWithPip) {
// Set an enter transition for when startAnimation gets called again
mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false,
+ SNAP_TO_2_50_50);
} else {
int finalClosingTaskId = closingSplitTaskId;
mRecentTasks.ifPresent(recentTasks ->
@@ -3556,6 +3589,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
mPausingTasks.clear();
+ if (enableFlexibleTwoAppSplit()) {
+ grantFocusForSnapPosition(enterTransition.mEnteringPosition);
+ }
});
if (info.getType() == TRANSIT_CHANGE && !isSplitActive()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
index 34d1011bac0e..f652e3149d9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -85,8 +85,8 @@ class WindowlessSnapshotWindowCreator {
final ActivityManager.TaskDescription taskDescription =
SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
- final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
- taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
+ final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, rootSurface,
+ wlw.mChildSurface, taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
runningTaskInfo.topActivityType, removeExecutor,
taskId, mStartingWindowRecordManager);
mStartingWindowRecordManager.addRecord(taskId, record);
@@ -96,14 +96,16 @@ class WindowlessSnapshotWindowCreator {
private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
private SurfaceControlViewHost mViewHost;
private SurfaceControl mChildSurface;
+ private SurfaceControl mRootSurface;
private final boolean mHasImeSurface;
- SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
- int bgColor, boolean hasImeSurface, int activityType,
+ SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl rootSurface,
+ SurfaceControl childSurface, int bgColor, boolean hasImeSurface, int activityType,
ShellExecutor removeExecutor, int id,
StartingSurfaceDrawer.StartingWindowRecordManager recordManager) {
super(activityType, removeExecutor, id, recordManager);
mViewHost = viewHost;
+ mRootSurface = rootSurface;
mChildSurface = childSurface;
mBGColor = bgColor;
mHasImeSurface = hasImeSurface;
@@ -145,6 +147,10 @@ class WindowlessSnapshotWindowCreator {
mTransactionPool.release(t);
mChildSurface = null;
}
+ if (mRootSurface != null && mRootSurface.isValid()) {
+ mRootSurface.release();
+ }
+ mRootSurface = null;
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index f5aaaad93229..ce98b03b77a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -137,8 +137,7 @@ public class DefaultSurfaceAnimator {
if (mClipRect != null) {
boolean needCrop = false;
mAnimClipRect.set(mClipRect);
- if (transformation.hasClipRect()
- && com.android.window.flags.Flags.respectAnimationClip()) {
+ if (transformation.hasClipRect()) {
mAnimClipRect.intersectUnchecked(transformation.getClipRect());
needCrop = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 938885cc1684..23dfb41d52c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
@@ -50,6 +51,7 @@ public class HomeTransitionObserver implements TransitionObserver,
private @NonNull final Context mContext;
private @NonNull final ShellExecutor mMainExecutor;
+ private IBinder mPendingStartDragTransition;
private Boolean mPendingHomeVisibilityUpdate;
public HomeTransitionObserver(@NonNull Context context,
@@ -63,31 +65,42 @@ public class HomeTransitionObserver implements TransitionObserver,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
- if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
- handleTransitionReadyWithBubbleAnything(info);
- } else {
- handleTransitionReady(info);
+ Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info);
+
+ if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) {
+ // Do not apply at the start of desktop drag as that updates launcher UI visibility.
+ // Store the value and apply with a next transition or when cancelling the
+ // desktop-drag transition.
+ storePendingHomeVisibilityUpdate(transition, homeVisibilityUpdate);
+ return;
+ }
+
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && info.getType() == TRANSIT_CONVERT_TO_BUBBLE
+ && homeVisibilityUpdate == null) {
+ // We are converting to bubble and we did not get a change to home visibility in this
+ // transition. Apply the value from start of drag.
+ homeVisibilityUpdate = mPendingHomeVisibilityUpdate;
+ }
+
+ if (homeVisibilityUpdate != null) {
+ mPendingHomeVisibilityUpdate = null;
+ mPendingStartDragTransition = null;
+ notifyHomeVisibilityChanged(homeVisibilityUpdate);
}
}
- private void handleTransitionReady(@NonNull TransitionInfo info) {
- for (TransitionInfo.Change change : info.getChanges()) {
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null
- || info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
- || taskInfo.displayId != DEFAULT_DISPLAY
- || taskInfo.taskId == -1
- || !taskInfo.isRunning) {
- continue;
- }
- Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info, change, taskInfo);
- if (homeVisibilityUpdate != null) {
- notifyHomeVisibilityChanged(homeVisibilityUpdate);
- }
+ private void storePendingHomeVisibilityUpdate(
+ IBinder transition, Boolean homeVisibilityUpdate) {
+ if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && !ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) {
+ return;
}
+ mPendingHomeVisibilityUpdate = homeVisibilityUpdate;
+ mPendingStartDragTransition = transition;
}
- private void handleTransitionReadyWithBubbleAnything(@NonNull TransitionInfo info) {
+ private Boolean getHomeVisibilityUpdate(TransitionInfo info) {
Boolean homeVisibilityUpdate = null;
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -97,29 +110,12 @@ public class HomeTransitionObserver implements TransitionObserver,
|| !taskInfo.isRunning) {
continue;
}
-
Boolean update = getHomeVisibilityUpdate(info, change, taskInfo);
if (update != null) {
homeVisibilityUpdate = update;
}
}
-
- if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) {
- // Do not apply at the start of desktop drag as that updates launcher UI visibility.
- // Store the value and apply with a next transition if needed.
- mPendingHomeVisibilityUpdate = homeVisibilityUpdate;
- return;
- }
-
- if (info.getType() == TRANSIT_CONVERT_TO_BUBBLE && homeVisibilityUpdate == null) {
- // We are converting to bubble and we did not get a change to home visibility in this
- // transition. Apply the value from start of drag.
- homeVisibilityUpdate = mPendingHomeVisibilityUpdate;
- }
- if (homeVisibilityUpdate != null) {
- mPendingHomeVisibilityUpdate = null;
- notifyHomeVisibilityChanged(homeVisibilityUpdate);
- }
+ return homeVisibilityUpdate;
}
private Boolean getHomeVisibilityUpdate(TransitionInfo info,
@@ -146,7 +142,24 @@ public class HomeTransitionObserver implements TransitionObserver,
@Override
public void onTransitionFinished(@NonNull IBinder transition,
- boolean aborted) {}
+ boolean aborted) {
+ if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) {
+ return;
+ }
+ // Handle the case where the DragToDesktop START transition is interrupted and we never
+ // receive a CANCEL/END transition.
+ if (mPendingStartDragTransition == null
+ || mPendingStartDragTransition != transition) {
+ return;
+ }
+ mPendingStartDragTransition = null;
+ if (aborted) return;
+
+ if (mPendingHomeVisibilityUpdate != null) {
+ notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate);
+ mPendingHomeVisibilityUpdate = null;
+ }
+ }
/**
* Sets the home transition listener that receives any transitions resulting in a change of
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
deleted file mode 100644
index 978b8da2eb6d..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.transition;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
-
-import android.annotation.NonNull;
-import android.os.RemoteException;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.IWindowContainerTransactionCallback;
-
-import com.android.internal.protolog.ProtoLog;
-
-/**
- * Utilities and interfaces for transition-like usage on top of the legacy app-transition and
- * synctransaction tools.
- */
-public class LegacyTransitions {
-
- /**
- * Interface for a "legacy" transition. Effectively wraps a sync callback + remoteAnimation
- * into one callback.
- */
- public interface ILegacyTransition {
- /**
- * Called when both the associated sync transaction finishes and the remote animation is
- * ready.
- */
- void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t);
- }
-
- /**
- * Makes sure that a remote animation and corresponding sync callback are called together
- * such that the sync callback is called first. This assumes that both the callback receiver
- * and the remoteanimation are in the same process so that order is preserved on both ends.
- */
- public static class LegacyTransition {
- private final ILegacyTransition mLegacyTransition;
- private int mSyncId = -1;
- private SurfaceControl.Transaction mTransaction;
- private int mTransit;
- private RemoteAnimationTarget[] mApps;
- private RemoteAnimationTarget[] mWallpapers;
- private RemoteAnimationTarget[] mNonApps;
- private IRemoteAnimationFinishedCallback mFinishCallback = null;
- private boolean mCancelled = false;
- private final SyncCallback mSyncCallback = new SyncCallback();
- private final RemoteAnimationAdapter mAdapter =
- new RemoteAnimationAdapter(new RemoteAnimationWrapper(), 0, 0);
-
- public LegacyTransition(@WindowManager.TransitionType int type,
- @NonNull ILegacyTransition legacyTransition) {
- mLegacyTransition = legacyTransition;
- mTransit = type;
- }
-
- public @WindowManager.TransitionType int getType() {
- return mTransit;
- }
-
- public IWindowContainerTransactionCallback getSyncCallback() {
- return mSyncCallback;
- }
-
- public RemoteAnimationAdapter getAdapter() {
- return mAdapter;
- }
-
- private class SyncCallback extends IWindowContainerTransactionCallback.Stub {
- @Override
- public void onTransactionReady(int id, SurfaceControl.Transaction t)
- throws RemoteException {
- ProtoLog.v(WM_SHELL_TRANSITIONS,
- "LegacyTransitions.onTransactionReady(): syncId=%d", id);
- mSyncId = id;
- mTransaction = t;
- checkApply(true /* log */);
- }
- }
-
- private class RemoteAnimationWrapper extends IRemoteAnimationRunner.Stub {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- mTransit = transit;
- mApps = apps;
- mWallpapers = wallpapers;
- mNonApps = nonApps;
- mFinishCallback = finishedCallback;
- checkApply(false /* log */);
- }
-
- @Override
- public void onAnimationCancelled() throws RemoteException {
- mCancelled = true;
- mApps = mWallpapers = mNonApps = null;
- checkApply(false /* log */);
- }
- }
-
-
- private void checkApply(boolean log) throws RemoteException {
- if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) {
- if (log) {
- ProtoLog.v(WM_SHELL_TRANSITIONS, "\tSkipping hasFinishedCb=%b canceled=%b",
- mFinishCallback != null, mCancelled);
- }
- return;
- }
- if (log) {
- ProtoLog.v(WM_SHELL_TRANSITIONS, "\tapply");
- }
- mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers,
- mNonApps, mFinishCallback, mTransaction);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 7871179a50de..42321e56e72b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -49,6 +49,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -233,7 +234,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 2b2cdf84005c..4511fbe10764 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -31,6 +31,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -159,7 +160,8 @@ public abstract class CarWindowDecorViewModel
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode) {
final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
index 01fc6440712d..adc5cdf340fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
@@ -43,7 +43,7 @@ class DesktopHandleManageWindowsMenu(
private val captionWidth: Int,
private val windowManagerWrapper: WindowManagerWrapper,
context: Context,
- snapshotList: List<Pair<Int, TaskSnapshot>>,
+ snapshotList: List<Pair<Int, TaskSnapshot?>>,
onIconClickListener: ((Int) -> Unit),
onOutsideClickListener: (() -> Unit)
) : ManageWindowsViewContainer(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index 02a5433147ca..3a75933f59d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -54,7 +54,7 @@ class DesktopHeaderManageWindowsMenu(
private val desktopUserRepositories: DesktopUserRepositories,
private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>,
private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
- snapshotList: List<Pair<Int, TaskSnapshot>>,
+ snapshotList: List<Pair<Int, TaskSnapshot?>>,
onIconClickListener: ((Int) -> Unit),
onOutsideClickListener: (() -> Unit)
) : ManageWindowsViewContainer(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 7ef1a93cbe45..f2ff39627362 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -17,11 +17,9 @@
package com.android.wm.shell.windowdecor;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -29,6 +27,7 @@ import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
@@ -79,9 +78,9 @@ import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewRootImpl;
-import android.view.WindowManager;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -121,7 +120,6 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction;
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
@@ -146,6 +144,7 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper;
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
@@ -153,18 +152,19 @@ import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
import com.android.wm.shell.windowdecor.tiling.SnapEventHandler;
+import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Pair;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
-import org.jetbrains.annotations.NotNull;
-
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.MainCoroutineDispatcher;
+import org.jetbrains.annotations.NotNull;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -207,7 +207,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
private final AppHandleEducationController mAppHandleEducationController;
private final AppToWebEducationController mAppToWebEducationController;
+ private final AppHandleAndHeaderVisibilityHelper mAppHandleAndHeaderVisibilityHelper;
private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
+ private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -294,6 +296,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
AppToWebEducationController appToWebEducationController,
+ AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
FocusTransitionObserver focusTransitionObserver,
@@ -332,12 +335,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
new AppHeaderViewHolder.Factory(),
+ new AppHandleViewHolder.Factory(),
rootTaskDisplayAreaOrganizer,
new SparseArray<>(),
interactionJankMonitor,
desktopTasksLimiter,
appHandleEducationController,
appToWebEducationController,
+ appHandleAndHeaderVisibilityHelper,
windowDecorCaptionHandleRepository,
activityOrientationChangeHandler,
new TaskPositionerFactory(),
@@ -380,12 +385,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
InteractionJankMonitor interactionJankMonitor,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
AppToWebEducationController appToWebEducationController,
+ AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
TaskPositionerFactory taskPositionerFactory,
@@ -421,6 +428,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
+ mAppHandleViewHolderFactory = appHandleViewHolderFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
mInputManager = mContext.getSystemService(InputManager.class);
@@ -431,6 +439,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopTasksLimiter = desktopTasksLimiter;
mAppHandleEducationController = appHandleEducationController;
mAppToWebEducationController = appToWebEducationController;
+ mAppHandleAndHeaderVisibilityHelper = appHandleAndHeaderVisibilityHelper;
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
@@ -484,7 +493,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
new DesktopModeOnTaskResizeAnimationListener());
mDesktopTasksController.setOnTaskRepositionAnimationListener(
new DesktopModeOnTaskRepositionAnimationListener());
- if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()
+ || DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
mRecentsTransitionHandler.addTransitionStateListener(
new DesktopModeRecentsTransitionStateListener());
}
@@ -528,6 +538,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
+ mAppHandleAndHeaderVisibilityHelper.setSplitScreenController(splitScreenController);
}
@Override
@@ -584,7 +595,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
if (decoration != null) {
@@ -598,8 +610,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion);
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion,
+ /*isMovingToBack= */ changeMode == TRANSIT_TO_BACK);
}
}
@@ -614,7 +626,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion);
+ mExclusionRegion, /* isMovingToBack= */ false);
}
@Override
@@ -814,9 +826,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return;
}
decoration.closeHandleMenu();
- // When the app enters split-select, the handle will no longer be visible, meaning
- // we shouldn't receive input for it any longer.
- decoration.disposeStatusBarInputLayer();
mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN);
@@ -976,6 +985,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private boolean mIsCustomHeaderGesture;
private boolean mIsResizeGesture;
private boolean mIsDragging;
+ private boolean mDragInterrupted;
private boolean mLongClickDisabled;
private int mDragPointerId = -1;
private MotionEvent mMotionEvent;
@@ -1213,9 +1223,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
View v, MotionEvent e) {
final int id = v.getId();
if (id == R.id.caption_handle) {
- handleCaptionThroughStatusBar(e, decoration);
+ handleCaptionThroughStatusBar(e, decoration,
+ /* interruptDragCallback= */
+ () -> {
+ mDragInterrupted = true;
+ setIsDragging(decoration, /* isDragging= */ false);
+ });
final boolean wasDragging = mIsDragging;
- updateDragStatus(e.getActionMasked());
+ updateDragStatus(decoration, e);
final boolean upOrCancel = e.getActionMasked() == ACTION_UP
|| e.getActionMasked() == ACTION_CANCEL;
if (wasDragging && upOrCancel) {
@@ -1231,6 +1246,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return false;
}
+ private void setIsDragging(
+ @Nullable DesktopModeWindowDecoration decor, boolean isDragging) {
+ mIsDragging = isDragging;
+ if (decor == null) return;
+ decor.setIsDragging(isDragging);
+ }
+
private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration,
RunningTaskInfo taskInfo, View v, MotionEvent e) {
final int id = v.getId();
@@ -1250,7 +1272,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getDisplayId(), e.getRawX(0),
e.getRawY(0));
- updateDragStatus(e.getActionMasked());
+ updateDragStatus(decoration, e);
mOnDragStartInitialBounds.set(initialBounds);
}
// Do not consume input event if a button is touched, otherwise it would
@@ -1277,7 +1299,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
newTaskBounds);
// Flip mIsDragging only if the bounds actually changed.
if (mIsDragging || !newTaskBounds.equals(mOnDragStartInitialBounds)) {
- updateDragStatus(e.getActionMasked());
+ updateDragStatus(decoration, e);
}
return true;
}
@@ -1310,7 +1332,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// onClick call that results.
return false;
} else {
- updateDragStatus(e.getActionMasked());
+ updateDragStatus(decoration, e);
return true;
}
}
@@ -1318,16 +1340,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return true;
}
- private void updateDragStatus(int eventAction) {
- switch (eventAction) {
+ private void updateDragStatus(DesktopModeWindowDecoration decor, MotionEvent e) {
+ switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- mIsDragging = false;
+ mDragInterrupted = false;
+ setIsDragging(decor, false /* isDragging */);
break;
}
case MotionEvent.ACTION_MOVE: {
- mIsDragging = true;
+ if (!mDragInterrupted) {
+ setIsDragging(decor, true /* isDragging */);
+ }
break;
}
}
@@ -1448,7 +1473,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (!mInImmersiveMode && (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
|| mTransitionDragActive)) {
- handleCaptionThroughStatusBar(ev, relevantDecor);
+ handleCaptionThroughStatusBar(ev, relevantDecor,
+ /* interruptDragCallback= */ () -> {});
}
}
handleEventOutsideCaption(ev, relevantDecor);
@@ -1488,7 +1514,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
* Turn on desktop mode if handle is dragged below status bar.
*/
private void handleCaptionThroughStatusBar(MotionEvent ev,
- DesktopModeWindowDecoration relevantDecor) {
+ DesktopModeWindowDecoration relevantDecor, Runnable interruptDragCallback) {
if (relevantDecor == null) {
if (ev.getActionMasked() == ACTION_UP) {
mMoveToDesktopAnimator = null;
@@ -1589,7 +1615,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor.mTaskSurface);
+ mMoveToDesktopAnimator, relevantDecor.mTaskSurface,
+ /* dragInterruptedCallback= */ () -> {
+ // Don't call into DesktopTasksController to cancel the
+ // transition here - the transition handler already handles
+ // that (including removing the visual indicator).
+ mTransitionDragActive = false;
+ mMoveToDesktopAnimator = null;
+ relevantDecor.handleDragInterrupted();
+ interruptDragCallback.run();
+ });
}
}
if (mMoveToDesktopAnimator != null) {
@@ -1717,32 +1752,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
- if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
- // If DisplayController doesn't have it tracked, it could be a private/managed display.
- return false;
- }
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- if (mSplitScreenController != null
- && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
- return false;
- }
- if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) {
- return false;
- }
- final boolean isOnLargeScreen =
- mDisplayController.getDisplay(taskInfo.displayId).getMinSizeDimensionDp()
- >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
- if (!DesktopModeStatus.canEnterDesktopMode(mContext)
- && DesktopModeStatus.overridesShowAppHandle(mContext) && !isOnLargeScreen) {
- // Devices with multiple screens may enable the app handle but it should not show on
- // small screens
- return false;
- }
- return DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(mContext)
- && !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
- && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
+ return mAppHandleAndHeaderVisibilityHelper.shouldShowAppHandleOrHeader(taskInfo);
}
private void createWindowDecoration(
@@ -1776,6 +1786,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mMainChoreographer,
mSyncQueue,
mAppHeaderViewHolderFactory,
+ mAppHandleViewHolderFactory,
mRootTaskDisplayAreaOrganizer,
mGenericLinksParser,
mAssistContentRequester,
@@ -1867,12 +1878,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion);
+ mExclusionRegion, /* isMovingToBack= */ false);
if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
}
+ @Nullable
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
.getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 30e5c2ae0914..bcf9396ff0c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -27,8 +27,10 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
+import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightId;
import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode;
import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopModeOrShowAppHandle;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.isDesktopModeSupportedOnDisplay;
import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragPositioningCallbackUtility.DragEventListener;
@@ -188,6 +190,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private ExclusionRegionListener mExclusionRegionListener;
private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
+ private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final MaximizeMenuFactory mMaximizeMenuFactory;
private final HandleMenuFactory mHandleMenuFactory;
@@ -207,9 +210,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopUserRepositories mDesktopUserRepositories;
private boolean mIsRecentsTransitionRunning = false;
-
+ private boolean mIsDragging = false;
private Runnable mLoadAppInfoRunnable;
private Runnable mSetAppInfoRunnable;
+ private boolean mIsMovingToBack;
public DesktopModeWindowDecoration(
Context context,
@@ -229,6 +233,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -240,10 +245,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
this (context, userContext, displayController, taskResourceLoader, splitScreenController,
desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler,
mainExecutor, mainDispatcher, bgScope, bgExecutor, choreographer, syncQueue,
- appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
- assistContentRequester, SurfaceControl.Builder::new,
- SurfaceControl.Transaction::new, WindowContainerTransaction::new,
- SurfaceControl::new, new WindowManagerWrapper(
+ appHeaderViewHolderFactory, appHandleViewHolderFactory,
+ rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
+ SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+ WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
context.getSystemService(WindowManager.class)),
new SurfaceControlViewHostFactory() {},
windowDecorViewHostSupplier,
@@ -271,6 +276,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -300,6 +306,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
+ mAppHandleViewHolderFactory = appHandleViewHolderFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
mAssistContentRequester = assistContentRequester;
@@ -460,7 +467,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus, displayExclusionRegion);
+ hasGlobalFocus, displayExclusionRegion, mIsMovingToBack);
if (!applyTransactionOnDraw) {
t.apply();
}
@@ -487,7 +494,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion,
+ boolean isMovingToBack) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) {
@@ -510,12 +518,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId)
.isTaskInFullImmersiveState(taskInfo.taskId);
+ mIsMovingToBack = isMovingToBack;
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
- displayExclusionRegion, mIsRecentsTransitionRunning,
- mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo));
+ mIsDragging, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ displayExclusionRegion,
+ /* shouldIgnoreCornerRadius= */ mIsRecentsTransitionRunning
+ && DesktopModeFlags
+ .ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(),
+ mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo),
+ mIsRecentsTransitionRunning, mIsMovingToBack);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -540,7 +553,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return;
}
- if (oldRootView != mResult.mRootView) {
+ if (DesktopModeFlags.SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX.isTrue()
+ ? (oldRootView != mResult.mRootView && taskInfo.isVisibleRequested)
+ : oldRootView != mResult.mRootView) {
disposeStatusBarInputLayer();
mWindowDecorViewHolder = createViewHolder();
// Load these only when first creating the view.
@@ -556,29 +571,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
});
}
- final Point position = new Point();
- if (isAppHandle(mWindowDecorViewHolder)) {
- position.set(determineHandlePosition());
- }
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyCaptionStateChanged();
}
Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
if (isAppHandle(mWindowDecorViewHolder)) {
- mWindowDecorViewHolder.bindData(new AppHandleViewHolder.HandleData(
- mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
- isCaptionVisible()
- ));
+ updateAppHandleViewHolder();
} else {
- mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
- mTaskInfo,
- DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
- inFullImmersive,
- hasGlobalFocus,
- /* maximizeHoverEnabled= */ canOpenMaximizeMenu(
- /* animatingTaskResizeOrReposition= */ false)
- ));
+ updateAppHeaderViewHolder(inFullImmersive, hasGlobalFocus);
}
Trace.endSection();
@@ -855,9 +856,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
}
+ /** Update the view holder for app handle. */
+ private void updateAppHandleViewHolder() {
+ if (!isAppHandle(mWindowDecorViewHolder)) return;
+ asAppHandle(mWindowDecorViewHolder).bindData(new AppHandleViewHolder.HandleData(
+ mTaskInfo, determineHandlePosition(), mResult.mCaptionWidth,
+ mResult.mCaptionHeight, isCaptionVisible()
+ ));
+ }
+
+ /** Update the view holder for app header. */
+ private void updateAppHeaderViewHolder(boolean inFullImmersive, boolean hasGlobalFocus) {
+ if (!isAppHeader(mWindowDecorViewHolder)) return;
+ asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
+ mTaskInfo,
+ DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
+ inFullImmersive,
+ hasGlobalFocus,
+ /* maximizeHoverEnabled= */ canOpenMaximizeMenu(
+ /* animatingTaskResizeOrReposition= */ false)
+ ));
+ }
+
private WindowDecorationViewHolder createViewHolder() {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
- return new AppHandleViewHolder(
+ return mAppHandleViewHolderFactory.create(
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
@@ -884,6 +907,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return viewHolder instanceof AppHandleViewHolder;
}
+ private boolean isAppHeader(WindowDecorationViewHolder viewHolder) {
+ return viewHolder instanceof AppHeaderViewHolder;
+ }
+
@Nullable
private AppHandleViewHolder asAppHandle(WindowDecorationViewHolder viewHolder) {
if (viewHolder instanceof AppHandleViewHolder) {
@@ -911,11 +938,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
boolean inFullImmersiveMode,
+ boolean isDragging,
@NonNull InsetsState displayInsetsState,
boolean hasGlobalFocus,
@NonNull Region displayExclusionRegion,
boolean shouldIgnoreCornerRadius,
- boolean shouldExcludeCaptionFromAppBounds) {
+ boolean shouldExcludeCaptionFromAppBounds,
+ boolean isRecentsTransitionRunning,
+ boolean isMovingToBack) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -932,10 +962,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// the first frame.
relayoutParams.mAsyncViewHost = isAppHandle;
- final boolean showCaption;
- if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
+ boolean showCaption;
+ // If this relayout is occurring from an observed TRANSIT_TO_BACK transition, do not
+ // show caption (this includes split select transition).
+ if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()
+ && isMovingToBack && !isDragging) {
+ showCaption = false;
+ } else if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) {
+ // If the task is being dragged, the caption should not be hidden so that it continues
+ // receiving input
+ showCaption = true;
+ } else if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()
+ && isRecentsTransitionRunning) {
+ // Caption should not be visible in recents.
+ showCaption = false;
+ } else if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
if (inFullImmersiveMode) {
- showCaption = isStatusBarVisible && !isKeyguardVisibleAndOccluded;
+ showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
} else {
showCaption = taskInfo.isFreeform()
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
@@ -1090,8 +1133,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return Resources.ID_NULL;
}
- private PointF calculateMaximizeMenuPosition(int menuWidth, int menuHeight) {
- final PointF position = new PointF();
+ private Point calculateMaximizeMenuPosition(int menuWidth, int menuHeight) {
+ final Point position = new Point();
final Resources resources = mContext.getResources();
final DisplayLayout displayLayout =
mDisplayController.getDisplayLayout(mTaskInfo.displayId);
@@ -1106,11 +1149,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final int[] maximizeButtonLocation = new int[2];
maximizeWindowButton.getLocationInWindow(maximizeButtonLocation);
- float menuLeft = (mPositionInParent.x + maximizeButtonLocation[0] - ((float) (menuWidth
- - maximizeWindowButton.getWidth()) / 2));
- float menuTop = (mPositionInParent.y + captionHeight);
- final float menuRight = menuLeft + menuWidth;
- final float menuBottom = menuTop + menuHeight;
+ int menuLeft = (mPositionInParent.x + maximizeButtonLocation[0] - (menuWidth
+ - maximizeWindowButton.getWidth()) / 2);
+ int menuTop = (mPositionInParent.y + captionHeight);
+ final int menuRight = menuLeft + menuWidth;
+ final int menuBottom = menuTop + menuHeight;
// If the menu is out of screen bounds, shift it as needed
if (menuLeft < 0) {
@@ -1122,7 +1165,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
menuTop = (displayHeight - menuHeight);
}
- return new PointF(menuLeft, menuTop);
+ return new Point(menuLeft, menuTop);
}
boolean isHandleMenuActive() {
@@ -1405,7 +1448,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
supportsMultiInstance,
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
- canEnterDesktopMode(mContext),
+ isDesktopModeSupportedOnDisplay(mContext, mDisplay),
isBrowserApp,
isBrowserApp ? getAppLink() : getBrowserLink(),
mResult.mCaptionWidth,
@@ -1676,6 +1719,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ /**
+ * Indicates that an app handle drag has been interrupted, this can happen e.g. if we receive an
+ * unknown transition during the drag-to-desktop transition.
+ */
+ void handleDragInterrupted() {
+ if (mResult.mRootView == null) return;
+ final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
+ handle.setHovered(false);
+ handle.setPressed(false);
+ }
+
private boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
@@ -1702,6 +1756,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
disposeStatusBarInputLayer();
+ mWindowDecorViewHolder.close();
mWindowDecorViewHolder = null;
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyNoCaptionHandle();
@@ -1761,7 +1816,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FULLSCREEN
? com.android.internal.R.dimen.status_bar_height_default
- : DesktopModeUtils.getAppHeaderHeightId();
+ : getDesktopViewAppHeaderHeightId();
}
private int getCaptionHeight(@WindowingMode int windowingMode) {
@@ -1792,9 +1847,25 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* <p> When a Recents transition is active we allow that transition to take ownership of the
* corner radius of its task surfaces, so each window decoration should stop updating the corner
* radius of its task surface during that time.
+ *
+ * We should not allow input to reach the input layer during a Recents transition, so
+ * update the handle view holder accordingly if transition status changes.
*/
void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
- mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ if (mIsRecentsTransitionRunning != isRecentsTransitionRunning) {
+ mIsRecentsTransitionRunning = isRecentsTransitionRunning;
+ if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
+ // We don't relayout decor on recents transition, so we need to call it directly.
+ relayout(mTaskInfo, mHasGlobalFocus, mRelayoutParams.mDisplayExclusionRegion);
+ }
+ }
+ }
+
+ /**
+ * Declares whether the window decoration is being dragged.
+ */
+ void setIsDragging(boolean isDragging) {
+ mIsDragging = isDragging;
}
/**
@@ -1852,6 +1923,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
+ AppHandleViewHolder.Factory appHandleViewHolderFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
@@ -1879,6 +1951,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
choreographer,
syncQueue,
appHeaderViewHolderFactory,
+ appHandleViewHolderFactory,
rootTaskDisplayAreaOrganizer,
genericLinksParser,
assistContentRequester,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index d36fc1247dad..a9df4bd1860d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -28,6 +28,7 @@ import static android.view.MotionEvent.ACTION_UP;
import android.annotation.NonNull;
import android.graphics.PointF;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -43,6 +44,8 @@ import androidx.annotation.Nullable;
* All touch events must be passed through this class to track a drag event.
*/
public class DragDetector {
+ private static final String TAG = "DragDetector";
+
private final MotionEventHandler mEventHandler;
private final PointF mInputDownPoint = new PointF();
@@ -109,8 +112,12 @@ public class DragDetector {
return mResultOfDownAction;
}
final int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+ // TODO(b/400635953): Separate the app header and its buttons'
+ // touch listeners so they're not handled by the same DragDetector.
if (dragPointerIndex == -1) {
- throw new IllegalStateException("Failed to find primary pointer!");
+ Log.w(TAG, "Invalid pointer index on ACTION_MOVE. Drag"
+ + " pointer id: " + mDragPointerId);
+ return mResultOfDownAction;
}
if (!mIsDragEvent) {
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 2d6f7459e0ae..732f04259fd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -173,6 +173,9 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio
return new Rect(mRepositionTaskBounds);
}
+ @Override
+ public void close() {}
+
private boolean isResizing() {
return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index ad2e23cb4028..a8a7032d0b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -39,7 +39,6 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
-import android.widget.TextView
import android.window.DesktopModeFlags
import android.window.SurfaceSyncGroup
import androidx.annotation.StringRes
@@ -59,10 +58,10 @@ import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.DrawableInsets
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
import com.android.wm.shell.windowdecor.common.calculateMenuPosition
-import com.android.wm.shell.windowdecor.common.DrawableInsets
-import com.android.wm.shell.windowdecor.common.createRippleDrawable
+import com.android.wm.shell.windowdecor.common.createBackgroundDrawable
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow
import com.android.wm.shell.windowdecor.extension.isPinned
@@ -109,12 +108,9 @@ class HandleMenu(
private val isViewAboveStatusBar: Boolean
get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform)
- private val pillElevation: Int = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_pill_elevation)
private val pillTopMargin: Int = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
- private val menuWidth = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_width) + pillElevation
+ private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_width)
private val menuHeight = getHandleMenuHeight()
private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
private val marginMenuStart = loadDimensionPixelSize(
@@ -399,8 +395,7 @@ class HandleMenu(
* Determines handle menu height based the max size and the visibility of pills.
*/
private fun getHandleMenuHeight(): Int {
- var menuHeight = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_height) + pillElevation
+ var menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_height)
if (!shouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_windowing_pill_height)
@@ -471,32 +466,20 @@ class HandleMenu(
val rootView = LayoutInflater.from(context)
.inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
- private val windowingButtonRippleRadius = context.resources
- .getDimensionPixelSize(R.dimen.desktop_mode_handle_menu_windowing_action_ripple_radius)
- private val windowingButtonDrawableInsets = DrawableInsets(
- vertical = context.resources
- .getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base),
- horizontal = context.resources
- .getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base)
- )
- private val windowingButtonDrawableInsetsLeft = DrawableInsets(
- vertical = context.resources
- .getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base),
- horizontalLeft = context.resources
- .getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_shift),
- )
- private val windowingButtonDrawableInsetsRight = DrawableInsets(
- vertical = context.resources
- .getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base),
- horizontalRight = context.resources
- .getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_shift)
- )
+ // Insets for ripple effect of App Info Pill. and Windowing Pill. buttons
+ val iconButtondrawableShiftInset = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_shift)
+ val iconButtondrawableBaseInset = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_base)
+ private val iconButtonRippleRadius = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius)
+ private val iconButtonDrawableInsetsBase = DrawableInsets(t = iconButtondrawableBaseInset,
+ b = iconButtondrawableBaseInset, l = iconButtondrawableBaseInset,
+ r = iconButtondrawableBaseInset)
+ private val iconButtonDrawableInsetsLeft = DrawableInsets(t = iconButtondrawableBaseInset,
+ b = iconButtondrawableBaseInset, l = iconButtondrawableShiftInset, r = 0)
+ private val iconButtonDrawableInsetsRight = DrawableInsets(t = iconButtondrawableBaseInset,
+ b = iconButtondrawableBaseInset, l = 0, r = iconButtondrawableShiftInset)
// App Info Pill.
private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill)
@@ -713,6 +696,12 @@ class HandleMenu(
collapseMenuButton.apply {
imageTintList = ColorStateList.valueOf(style.textColor)
this.taskInfo = this@HandleMenuView.taskInfo
+
+ background = createBackgroundDrawable(
+ color = style.textColor,
+ cornerRadius = iconButtonRippleRadius,
+ drawableInsets = iconButtonDrawableInsetsBase
+ )
}
appNameView.setTextColor(style.textColor)
appNameView.startMarquee()
@@ -740,45 +729,39 @@ class HandleMenu(
desktopBtn.isEnabled = !taskInfo.isFreeform
desktopBtn.imageTintList = style.windowingButtonColor
- val startInsets = if (context.isRtl) {
- windowingButtonDrawableInsetsRight
- } else {
- windowingButtonDrawableInsetsLeft
- }
- val endInsets = if (context.isRtl) {
- windowingButtonDrawableInsetsLeft
- } else {
- windowingButtonDrawableInsetsRight
- }
+ val startInsets = if (context.isRtl) iconButtonDrawableInsetsRight
+ else iconButtonDrawableInsetsLeft
+ val endInsets = if (context.isRtl) iconButtonDrawableInsetsLeft
+ else iconButtonDrawableInsetsRight
fullscreenBtn.apply {
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = style.textColor,
- cornerRadius = windowingButtonRippleRadius,
+ cornerRadius = iconButtonRippleRadius,
drawableInsets = startInsets
)
}
splitscreenBtn.apply {
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = style.textColor,
- cornerRadius = windowingButtonRippleRadius,
- drawableInsets = windowingButtonDrawableInsets
+ cornerRadius = iconButtonRippleRadius,
+ drawableInsets = iconButtonDrawableInsetsBase
)
}
floatingBtn.apply {
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = style.textColor,
- cornerRadius = windowingButtonRippleRadius,
- drawableInsets = windowingButtonDrawableInsets
+ cornerRadius = iconButtonRippleRadius,
+ drawableInsets = iconButtonDrawableInsetsBase
)
}
desktopBtn.apply {
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = style.textColor,
- cornerRadius = windowingButtonRippleRadius,
+ cornerRadius = iconButtonRippleRadius,
drawableInsets = endInsets
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index bdde096d4882..e8aac39a650c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -22,7 +22,7 @@ import android.annotation.DrawableRes
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
-import android.graphics.drawable.RippleDrawable
+import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
@@ -30,11 +30,11 @@ import android.view.ViewStub
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ProgressBar
+import android.window.DesktopModeFlags
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.content.ContextCompat
import com.android.wm.shell.R
-import android.window.DesktopModeFlags
private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
private const val MAX_DRAWABLE_ALPHA = 255
@@ -109,14 +109,14 @@ class MaximizeButtonView(context: Context, attrs: AttributeSet) : FrameLayout(co
darkMode: Boolean,
iconForegroundColor: ColorStateList? = null,
baseForegroundColor: Int? = null,
- rippleDrawable: RippleDrawable? = null
+ backgroundDrawable: Drawable? = null
) {
if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) {
requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" }
requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" }
- requireNotNull(rippleDrawable) { "Ripple drawable must be non-null" }
+ requireNotNull(backgroundDrawable) { "Background drawable must be non-null" }
maximizeWindow.imageTintList = iconForegroundColor
- maximizeWindow.background = rippleDrawable
+ maximizeWindow.background = backgroundDrawable
stubProgressBarContainer.setOnInflateListener { _, inflated ->
val progressBar = (inflated as FrameLayout)
.requireViewById(R.id.progress_bar) as ProgressBar
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index ad3525af3f94..5d1a7a0cc3a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -26,7 +26,7 @@ import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Paint
import android.graphics.PixelFormat
-import android.graphics.PointF
+import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
@@ -90,7 +90,7 @@ class MaximizeMenu(
private val displayController: DisplayController,
private val taskInfo: RunningTaskInfo,
private val decorWindowContext: Context,
- private val positionSupplier: (Int, Int) -> PointF,
+ private val positionSupplier: (Int, Int) -> Point,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
) {
private var maximizeMenu: AdditionalViewHostViewContainer? = null
@@ -100,14 +100,14 @@ class MaximizeMenu(
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
- private lateinit var menuPosition: PointF
+ private lateinit var menuPosition: Point
private val menuPadding = loadDimensionPixelSize(R.dimen.desktop_mode_menu_padding)
/** Position the menu relative to the caption's position. */
fun positionMenu(t: Transaction) {
menuPosition = positionSupplier(maximizeMenuView?.measureWidth() ?: 0,
maximizeMenuView?.measureHeight() ?: 0)
- t.setPosition(leash, menuPosition.x, menuPosition.y)
+ t.setPosition(leash, menuPosition.x.toFloat(), menuPosition.y.toFloat())
}
/** Creates and shows the maximize window. */
@@ -208,8 +208,8 @@ class MaximizeMenu(
val menuHeight = menuView.measureHeight()
menuPosition = positionSupplier(menuWidth, menuHeight)
val lp = WindowManager.LayoutParams(
- menuWidth.toInt(),
- menuHeight.toInt(),
+ menuWidth,
+ menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
@@ -222,7 +222,7 @@ class MaximizeMenu(
// Bring menu to front when open
t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU)
- .setPosition(leash, menuPosition.x, menuPosition.y)
+ .setPosition(leash, menuPosition.x.toFloat(), menuPosition.y.toFloat())
.setCornerRadius(leash, cornerRadius)
.show(leash)
maximizeMenu =
@@ -1047,7 +1047,7 @@ interface MaximizeMenuFactory {
displayController: DisplayController,
taskInfo: RunningTaskInfo,
decorWindowContext: Context,
- positionSupplier: (Int, Int) -> PointF,
+ positionSupplier: (Int, Int) -> Point,
transactionSupplier: Supplier<Transaction>
): MaximizeMenu
}
@@ -1060,7 +1060,7 @@ object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
displayController: DisplayController,
taskInfo: RunningTaskInfo,
decorWindowContext: Context,
- positionSupplier: (Int, Int) -> PointF,
+ positionSupplier: (Int, Int) -> Point,
transactionSupplier: Supplier<Transaction>
): MaximizeMenu {
return MaximizeMenu(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index 22bc9782170b..cf536eba8382 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -42,8 +42,6 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
.setDuration(ANIMATION_DURATION.toLong())
.apply {
val t = SurfaceControl.Transaction()
- val cornerRadius = context.resources
- .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat()
addUpdateListener {
setTaskPosition(mostRecentInput.x, mostRecentInput.y)
t.setScale(taskSurface, scale, scale)
@@ -57,6 +55,8 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
val taskId get() = taskInfo.taskId
val position: PointF = PointF(0.0f, 0.0f)
+ val cornerRadius: Float = context.resources
+ .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat()
/**
* Whether motion events from the drag gesture should affect the dragged surface or not. Used
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index 1b0e0f70ed21..eb324f74ca82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -294,6 +294,10 @@ class MultiDisplayVeiledResizeTaskPositioner(
return Rect(repositionTaskBounds)
}
+ override fun close() {
+ displayController.removeDisplayWindowListener(this)
+ }
+
private fun resetVeilIfVisible() {
if (isResizingOrAnimatingResize) {
desktopWindowDecoration.hideResizeVeil()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
index 63b288d133dd..06e5380fa1de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
@@ -41,4 +41,10 @@ public interface TaskDragResizer {
*/
void removeDragEventListener(
DragPositioningCallbackUtility.DragEventListener dragEventListener);
+
+ /**
+ * Releases any resources associated with this TaskDragResizer. This should be called when the
+ * associated window is closed.
+ */
+ void close();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index d2c79d76e6c1..7e941ec0d31a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -205,6 +205,9 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
return new Rect(mRepositionTaskBounds);
}
+ @Override
+ public void close() {}
+
private boolean isResizing() {
return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 1563259f4a1a..5e4a0a5860f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -83,12 +84,14 @@ public interface WindowDecorViewModel {
* @param taskSurface the surface of the task
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
+ * @param changeMode the type of change to the task
*/
void onTaskChanging(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT);
+ SurfaceControl.Transaction finishT,
+ @TransitionInfo.TransitionMode int changeMode);
/**
* Notifies that the given task is about to close to give the window decoration a chance to
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 7baef2b2dc97..91a899c09407 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
@@ -361,6 +361,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mRootView = rootView;
final boolean fontScaleChanged = mWindowDecorConfig != null
&& mWindowDecorConfig.fontScale != mTaskInfo.configuration.fontScale;
+ final boolean localeListChanged = mWindowDecorConfig != null
+ && !mWindowDecorConfig.getLocales()
+ .equals(mTaskInfo.getConfiguration().getLocales());
final int oldDensityDpi = mWindowDecorConfig != null
? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
final int oldNightMode = mWindowDecorConfig != null
@@ -376,7 +379,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|| oldLayoutResId != mLayoutResId
|| oldNightMode != newNightMode
|| mDecorWindowContext == null
- || fontScaleChanged) {
+ || fontScaleChanged
+ || localeListChanged) {
releaseViews(wct);
if (!obtainDisplayOrRegisterListener()) {
@@ -695,6 +699,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
public void close() {
Trace.beginSection("WindowDecoration#close");
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ if (mTaskDragResizer != null) {
+ mTaskDragResizer.close();
+ }
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
releaseViews(wct);
mTaskOrganizer.applyTransaction(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
new file mode 100644
index 000000000000..39ccf5bd03a7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.common
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.Context
+import android.view.WindowManager
+import android.window.DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity.Companion.isWallpaperTask
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.splitscreen.SplitScreenController
+
+/**
+ * Resolves whether, given a task and its associated display that it is currently on, to show the
+ * app handle/header or not.
+ */
+class AppHandleAndHeaderVisibilityHelper (
+ private val context: Context,
+ private val displayController: DisplayController,
+ private val desktopModeCompatPolicy: DesktopModeCompatPolicy
+) {
+ var splitScreenController: SplitScreenController? = null
+
+ /**
+ * Returns, given a task's attribute and its display attribute, whether the app
+ * handle/header should show or not for this task.
+ */
+ fun shouldShowAppHandleOrHeader(taskInfo: ActivityManager.RunningTaskInfo): Boolean {
+ if (!ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY.isTrue) {
+ return allowedForTask(taskInfo)
+ }
+ return allowedForTask(taskInfo) && allowedForDisplay(taskInfo.displayId)
+ }
+
+ private fun allowedForTask(taskInfo: ActivityManager.RunningTaskInfo): Boolean {
+ // TODO (b/382023296): Remove once we no longer rely on
+ // Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay
+ if (displayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false
+ }
+ if (taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) return true
+ if (splitScreenController?.isTaskRootOrStageRoot(taskInfo.taskId) == true) {
+ return false
+ }
+
+ if (desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) {
+ return false
+ }
+
+ // TODO (b/382023296): Remove once we no longer rely on
+ // Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay
+ val isOnLargeScreen =
+ displayController.getDisplay(taskInfo.displayId).minSizeDimensionDp >=
+ WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ if (!DesktopModeStatus.canEnterDesktopMode(context)
+ && DesktopModeStatus.overridesShowAppHandle(context)
+ && !isOnLargeScreen
+ ) {
+ // Devices with multiple screens may enable the app handle but it should not show on
+ // small screens
+ return false
+ }
+ return DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)
+ && !isWallpaperTask(taskInfo)
+ && taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
+ && taskInfo.activityType == WindowConfiguration.ACTIVITY_TYPE_STANDARD
+ && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop
+ }
+
+ private fun allowedForDisplay(displayId: Int): Boolean {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ val display = displayController.getDisplay(displayId)
+ if (display == null) return false
+
+ if (DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)) {
+ return true
+ }
+ // If on default display and on Large Screen (unfolded), show app handle
+ return DesktopModeStatus.overridesShowAppHandle(context)
+ && display.minSizeDimensionDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
index e18239d3eb70..f08cfa987cc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
@@ -16,14 +16,12 @@
package com.android.wm.shell.windowdecor.common
import android.annotation.ColorInt
+import android.content.res.ColorStateList
import android.graphics.Color
+import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
-import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
-import com.android.wm.shell.windowdecor.common.OPACITY_11
-import com.android.wm.shell.windowdecor.common.OPACITY_15
-import android.content.res.ColorStateList
/**
* Represents drawable insets, specifying the number of pixels to inset a drawable from its bounds.
@@ -49,40 +47,30 @@ fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int {
}
/**
- * Creates a RippleDrawable with specified color, corner radius, and insets.
+ * Creates a background drawable with specified color, corner radius, and insets.
*/
-fun createRippleDrawable(
- @ColorInt color: Int,
- cornerRadius: Int,
- drawableInsets: DrawableInsets,
-): RippleDrawable {
- return RippleDrawable(
- ColorStateList(
+fun createBackgroundDrawable(
+ @ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets
+): Drawable = LayerDrawable(arrayOf(
+ ShapeDrawable().apply {
+ shape = RoundRectShape(
+ FloatArray(8) { cornerRadius.toFloat() },
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ setTintList(ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_hovered),
intArrayOf(android.R.attr.state_pressed),
- intArrayOf(),
),
intArrayOf(
replaceColorAlpha(color, OPACITY_11),
replaceColorAlpha(color, OPACITY_15),
- Color.TRANSPARENT,
)
- ),
- null /* content */,
- LayerDrawable(arrayOf(
- ShapeDrawable().apply {
- shape = RoundRectShape(
- FloatArray(8) { cornerRadius.toFloat() },
- null /* inset */,
- null /* innerRadii */
- )
- paint.color = Color.WHITE
- }
- )).apply {
- require(numberOfLayers == 1) { "Must only contain one layer" }
- setLayerInset(0 /* index */,
- drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b)
- }
- )
+ ))
+ }
+)).apply {
+ require(numberOfLayers == 1) { "Must only contain one layer" }
+ setLayerInset(/* index= */ 0,
+ drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
index 801048adda4d..957898fd0088 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
+import android.os.LocaleList
import android.os.UserHandle
import androidx.tracing.Trace
import com.android.internal.annotations.VisibleForTesting
@@ -80,6 +81,13 @@ class WindowDecorTaskResourceLoader(
*/
private val existingTasks = mutableSetOf<Int>()
+ /**
+ * A map of task -> localeList to keep track of the language of app name that's currently
+ * cached in |taskToResourceCache|.
+ */
+ @VisibleForTesting
+ val localeListOnCache = ConcurrentHashMap<Int, LocaleList>()
+
init {
shellInit.addInitCallback(this::onInit, this)
}
@@ -99,11 +107,14 @@ class WindowDecorTaskResourceLoader(
fun getName(taskInfo: RunningTaskInfo): CharSequence {
checkWindowDecorExists(taskInfo)
val cachedResources = taskToResourceCache[taskInfo.taskId]
- if (cachedResources != null) {
+ val localeListActiveOnCacheTime = localeListOnCache[taskInfo.taskId]
+ if (cachedResources != null &&
+ taskInfo.getConfiguration().getLocales().equals(localeListActiveOnCacheTime)) {
return cachedResources.appName
}
val resources = loadAppResources(taskInfo)
taskToResourceCache[taskInfo.taskId] = resources
+ localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
return resources.appName
}
@@ -117,6 +128,7 @@ class WindowDecorTaskResourceLoader(
}
val resources = loadAppResources(taskInfo)
taskToResourceCache[taskInfo.taskId] = resources
+ localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
return resources.appIcon
}
@@ -130,6 +142,7 @@ class WindowDecorTaskResourceLoader(
}
val resources = loadAppResources(taskInfo)
taskToResourceCache[taskInfo.taskId] = resources
+ localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
return resources.veilIcon
}
@@ -142,6 +155,7 @@ class WindowDecorTaskResourceLoader(
fun onWindowDecorClosed(taskInfo: RunningTaskInfo) {
existingTasks.remove(taskInfo.taskId)
taskToResourceCache.remove(taskInfo.taskId)
+ localeListOnCache.remove(taskInfo.taskId)
}
private fun checkWindowDecorExists(taskInfo: RunningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index cb45c1732476..57f8046065b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -16,6 +16,9 @@
package com.android.wm.shell.windowdecor.tiling
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.graphics.Path
@@ -144,7 +147,6 @@ class DesktopTilingDividerWindowManager(
* @param relativeLeash the task leash that the TilingDividerView should be shown on top of.
*/
fun generateViewHost(relativeLeash: SurfaceControl) {
- val t = transactionSupplier.get()
val surfaceControlViewHost =
SurfaceControlViewHost(context, context.display, this, "DesktopTilingManager")
val dividerView =
@@ -155,22 +157,40 @@ class DesktopTilingDividerWindowManager(
val tmpDividerBounds = Rect()
getDividerBounds(tmpDividerBounds)
dividerView.setup(this, tmpDividerBounds, handleRegionSize, isDarkMode)
- t.setRelativeLayer(leash, relativeLeash, 1)
- .setPosition(
- leash,
- dividerBounds.left.toFloat() - maxRoundedCornerRadius,
- dividerBounds.top.toFloat(),
- )
- .show(leash)
- syncQueue.runInSync { transaction ->
- transaction.merge(t)
- t.close()
- }
- dividerShown = true
+ val dividerAnimatorT = transactionSupplier.get()
+ val dividerAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = DIVIDER_FADE_IN_ALPHA_DURATION
+ addUpdateListener {
+ dividerAnimatorT.setAlpha(leash, animatedValue as Float).apply()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ dividerAnimatorT
+ .setRelativeLayer(leash, relativeLeash, 1)
+ .setPosition(
+ leash,
+ dividerBounds.left.toFloat() - maxRoundedCornerRadius,
+ dividerBounds.top.toFloat(),
+ )
+ .setAlpha(leash, 0f)
+ .show(leash)
+ .apply()
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ dividerAnimatorT.setAlpha(leash, 1f).apply()
+ dividerShown = true
+ }
+ }
+ )
+ }
+ dividerAnimator.start()
viewHost = surfaceControlViewHost
- dividerView.addOnLayoutChangeListener(this)
tilingDividerView = dividerView
updateTouchRegion()
+ dividerView.addOnLayoutChangeListener(this)
}
/** Changes divider colour if dark/light mode is toggled. */
@@ -311,4 +331,8 @@ class DesktopTilingDividerWindowManager(
)
.maxOf { position -> display.getRoundedCorner(position)?.getRadius() ?: 0 }
}
+
+ companion object {
+ private const val DIVIDER_FADE_IN_ALPHA_DURATION = 300L
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index a45df045041f..7c5f34f979cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.windowdecor.tiling
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
@@ -28,9 +29,11 @@ import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.internal.annotations.VisibleForTesting
@@ -133,10 +136,10 @@ class DesktopTilingWindowDecoration(
isDarkMode = isTaskInDarkMode(taskInfo)
// Observe drag resizing to break tiling if a task is drag resized.
desktopModeWindowDecoration.addDragResizeListener(this)
-
+ val callback = { initTilingForDisplayIfNeeded(taskInfo.configuration, isFirstTiledApp) }
if (isTiled) {
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
- toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentBounds)
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentBounds, callback)
} else {
// Handle the case where we attempt to snap resize when already snap resized: the task
// position won't need to change but we want to animate the surface going back to the
@@ -147,10 +150,12 @@ class DesktopTilingWindowDecoration(
resizeMetadata.getLeash(),
startBounds = currentBounds,
endBounds = destinationBounds,
+ callback,
)
+ } else {
+ callback.invoke()
}
}
- initTilingForDisplayIfNeeded(taskInfo.configuration, isFirstTiledApp)
return isTiled
}
@@ -422,6 +427,8 @@ class DesktopTilingWindowDecoration(
change.taskInfo?.let {
if (it.isFullscreen || isMinimized(change.mode, info.type)) {
removeTaskIfTiled(it.taskId, /* taskVanished= */ false, it.isFullscreen)
+ } else if (isEnteringPip(change, info.type)) {
+ removeTaskIfTiled(it.taskId, /* taskVanished= */ true, it.isFullscreen)
}
}
}
@@ -434,6 +441,27 @@ class DesktopTilingWindowDecoration(
infoType == TRANSIT_OPEN))
}
+ private fun isEnteringPip(change: Change, transitType: Int): Boolean {
+ if (change.taskInfo != null && change.taskInfo?.windowingMode == WINDOWING_MODE_PINNED) {
+ // - TRANSIT_PIP: type (from RootWindowContainer)
+ // - TRANSIT_OPEN (from apps that enter PiP instantly on opening, mostly from
+ // CTS/Flicker tests).
+ // - TRANSIT_TO_FRONT, though uncommon with triggering PiP, should semantically also
+ // be allowed to animate if the task in question is pinned already - see b/308054074.
+ // - TRANSIT_CHANGE: This can happen if the request to enter PIP happens when we are
+ // collecting for another transition, such as TRANSIT_CHANGE (display rotation).
+ if (
+ transitType == TRANSIT_PIP ||
+ transitType == TRANSIT_OPEN ||
+ transitType == TRANSIT_TO_FRONT ||
+ transitType == TRANSIT_CHANGE
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
class AppResizingHelper(
val taskInfo: RunningTaskInfo,
val desktopModeWindowDecoration: DesktopModeWindowDecoration,
@@ -550,12 +578,16 @@ class DesktopTilingWindowDecoration(
taskVanished: Boolean = false,
shouldDelayUpdate: Boolean = false,
) {
+ val taskRepository = desktopUserRepositories.current
if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) {
removeTask(leftTaskResizingHelper, taskVanished, shouldDelayUpdate)
leftTaskResizingHelper = null
- rightTaskResizingHelper
- ?.desktopModeWindowDecoration
- ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ val taskId = rightTaskResizingHelper?.taskInfo?.taskId
+ if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ rightTaskResizingHelper
+ ?.desktopModeWindowDecoration
+ ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ }
tearDownTiling()
return
}
@@ -563,9 +595,12 @@ class DesktopTilingWindowDecoration(
if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) {
removeTask(rightTaskResizingHelper, taskVanished, shouldDelayUpdate)
rightTaskResizingHelper = null
- leftTaskResizingHelper
- ?.desktopModeWindowDecoration
- ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ val taskId = leftTaskResizingHelper?.taskInfo?.taskId
+ if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ leftTaskResizingHelper
+ ?.desktopModeWindowDecoration
+ ?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
+ }
tearDownTiling()
}
}
@@ -600,7 +635,6 @@ class DesktopTilingWindowDecoration(
fun onOverviewAnimationStateChange(isRunning: Boolean) {
if (!isTilingManagerInitialised) return
-
if (isRunning) {
desktopTilingDividerWindowManager?.hideDividerBar()
} else if (allTiledTasksVisible()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 2948fdaf16af..0985587a330e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -49,7 +49,7 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
* It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
*/
-internal class AppHandleViewHolder(
+class AppHandleViewHolder(
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: OnClickListener,
@@ -66,7 +66,7 @@ internal class AppHandleViewHolder(
val position: Point,
val width: Int,
val height: Int,
- val isCaptionVisible: Boolean
+ val showInputLayer: Boolean
) : Data()
private lateinit var taskInfo: RunningTaskInfo
@@ -101,7 +101,7 @@ internal class AppHandleViewHolder(
}
override fun bindData(data: HandleData) {
- bindData(data.taskInfo, data.position, data.width, data.height, data.isCaptionVisible)
+ bindData(data.taskInfo, data.position, data.width, data.height, data.showInputLayer)
}
private fun bindData(
@@ -109,14 +109,13 @@ internal class AppHandleViewHolder(
position: Point,
width: Int,
height: Int,
- isCaptionVisible: Boolean
+ showInputLayer: Boolean
) {
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
this.taskInfo = taskInfo
// If handle is not in status bar region(i.e., bottom stage in vertical split),
// do not create an input layer
- if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
- if (!isCaptionVisible) {
+ if (position.y >= SystemBarUtils.getStatusBarHeight(context) || !showInputLayer) {
disposeStatusBarInputLayer()
return
}
@@ -274,4 +273,27 @@ internal class AppHandleViewHolder(
}
animator.start()
}
+
+ override fun close() {}
+
+ /** Factory class for creating [AppHandleViewHolder] objects. */
+ class Factory {
+ /**
+ * Create a [AppHandleViewHolder] object to handle caption view and status bar
+ * input layer logic.
+ */
+ fun create(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: OnClickListener,
+ windowManagerWrapper: WindowManagerWrapper,
+ handler: Handler,
+ ): AppHandleViewHolder = AppHandleViewHolder(
+ rootView,
+ onCaptionTouchListener,
+ onCaptionButtonClickListener,
+ windowManagerWrapper,
+ handler,
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index eb8b617df4ce..30712b55bdfa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -23,10 +23,6 @@ import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Rect
-import android.graphics.drawable.LayerDrawable
-import android.graphics.drawable.RippleDrawable
-import android.graphics.drawable.ShapeDrawable
-import android.graphics.drawable.shapes.RoundRectShape
import android.os.Bundle
import android.view.View
import android.view.View.OnLongClickListener
@@ -55,14 +51,12 @@ import com.android.internal.R.color.materialColorSurfaceDim
import com.android.wm.shell.R
import com.android.wm.shell.windowdecor.MaximizeButtonView
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.DrawableInsets
import com.android.wm.shell.windowdecor.common.OPACITY_100
-import com.android.wm.shell.windowdecor.common.OPACITY_11
-import com.android.wm.shell.windowdecor.common.OPACITY_15
import com.android.wm.shell.windowdecor.common.OPACITY_55
import com.android.wm.shell.windowdecor.common.OPACITY_65
import com.android.wm.shell.windowdecor.common.Theme
-import com.android.wm.shell.windowdecor.common.DrawableInsets
-import com.android.wm.shell.windowdecor.common.createRippleDrawable
+import com.android.wm.shell.windowdecor.common.createBackgroundDrawable
import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance
import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance
@@ -385,7 +379,7 @@ class AppHeaderViewHolder(
val colorStateList = ColorStateList.valueOf(foregroundColor).withAlpha(foregroundAlpha)
// App chip.
openMenuButton.apply {
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = appChipDrawableInsets,
@@ -396,11 +390,12 @@ class AppHeaderViewHolder(
setTextColor(colorStateList)
}
appIconImageView.imageAlpha = foregroundAlpha
+ defaultFocusHighlightEnabled = false
}
// Minimize button.
minimizeWindowButton.apply {
imageTintList = colorStateList
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = minimizeDrawableInsets
@@ -413,7 +408,7 @@ class AppHeaderViewHolder(
darkMode = header.appTheme == Theme.DARK,
iconForegroundColor = colorStateList,
baseForegroundColor = foregroundColor,
- rippleDrawable = createRippleDrawable(
+ backgroundDrawable = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = maximizeDrawableInsets
@@ -451,7 +446,7 @@ class AppHeaderViewHolder(
// Close button.
closeWindowButton.apply {
imageTintList = colorStateList
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = closeDrawableInsets
@@ -471,11 +466,7 @@ class AppHeaderViewHolder(
override fun onHandleMenuOpened() {}
- override fun onHandleMenuClosed() {
- openMenuButton.post {
- openMenuButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
- }
- }
+ override fun onHandleMenuClosed() {}
fun onMaximizeWindowHoverExit() {
maximizeButtonView.cancelHoverAnimation()
@@ -725,6 +716,11 @@ class AppHeaderViewHolder(
Configuration.UI_MODE_NIGHT_YES
}
+ override fun close() {
+ // Should not fire long press events after closing the window decoration.
+ maximizeWindowButton.cancelLongPress()
+ }
+
companion object {
private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
index 1fe743da966a..cd202bfbd29e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
@@ -24,7 +24,7 @@ import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Da
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
*/
-abstract class WindowDecorationViewHolder<T : Data>(rootView: View) {
+abstract class WindowDecorationViewHolder<T : Data>(rootView: View) : AutoCloseable {
val context: Context = rootView.context
/**
@@ -39,6 +39,9 @@ abstract class WindowDecorationViewHolder<T : Data>(rootView: View) {
/** Callback when the handle menu is closed. */
abstract fun onHandleMenuClosed()
+ /** Callback when the window decoration is destroyed. */
+ abstract override fun close()
+
/** Data clas that contains the information needed to update the view holder. */
abstract class Data
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index d73d08c032f9..c3abe73c1d01 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.flicker
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_HEIGHT
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_WIDTH
import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppLayerCoversFullScreenAtEnd
import android.tools.flicker.assertors.assertions.ResizeVeilKeepsIncreasingInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
@@ -99,6 +100,59 @@ class DesktopModeFlickerScenarios {
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+ val ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> =
+ transitions.filter {
+ it.type == TransitionType.ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
+ )
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ val EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> =
+ transitions.filter {
+ it.type == TransitionType.EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+ AppLayerCoversFullScreenAtEnd(DESKTOP_MODE_APP),
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesInvisible(DESKTOP_WALLPAPER)
+ )
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
// Use this scenario for closing an app in desktop windowing, except the last app. For the
// last app use CLOSE_LAST_APP scenario
val CLOSE_APP =
@@ -361,11 +415,10 @@ class DesktopModeFlickerScenarios {
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
+ ): Collection<Transition> =
+ transitions.filter {
it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
}
- }
}
),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
@@ -385,11 +438,10 @@ class DesktopModeFlickerScenarios {
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
+ ): Collection<Transition> =
+ transitions.filter {
it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
}
- }
}
),
assertions =
@@ -410,11 +462,10 @@ class DesktopModeFlickerScenarios {
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
+ ): Collection<Transition> =
+ transitions.filter {
it.type == TransitionType.TO_FRONT
}
- }
}
),
assertions =
@@ -434,9 +485,8 @@ class DesktopModeFlickerScenarios {
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter { it.type == TransitionType.OPEN }
- }
+ ): Collection<Transition> =
+ transitions.filter { it.type == TransitionType.OPEN }
}
),
assertions =
@@ -512,9 +562,8 @@ class DesktopModeFlickerScenarios {
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter { it.type == TransitionType.OPEN }
- }
+ ): Collection<Transition> =
+ transitions.filter { it.type == TransitionType.OPEN }
}
),
assertions =
@@ -553,11 +602,10 @@ class DesktopModeFlickerScenarios {
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return listOf(transitions
+ ): Collection<Transition> =
+ listOf(transitions
.filter { it.type == TransitionType.OPEN }
.maxByOrNull { it.id }!!)
- }
}
),
assertions =
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt
new file mode 100644
index 000000000000..4603e4ed8f8e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
+import com.android.wm.shell.scenarios.EnterDesktopFromKeyboardShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterDesktopWithKeyboardShortcut : EnterDesktopFromKeyboardShortcut() {
+ @ExpectedScenarios(["ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT"])
+ @Test
+ override fun enterDesktopFromKeyboardShortcut() = super.enterDesktopFromKeyboardShortcut()
+
+ companion object {
+
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt
new file mode 100644
index 000000000000..d9fd339dbf55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT
+import com.android.wm.shell.scenarios.ExitDesktopFromKeyboardShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ExitDesktopToFullScreenWithKeyboardShortcut : ExitDesktopFromKeyboardShortcut() {
+ @ExpectedScenarios(["EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT"])
+ @Test
+ override fun exitDesktopToFullScreenFromKeyboardShortcut() =
+ super.exitDesktopToFullScreenFromKeyboardShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt
new file mode 100644
index 000000000000..32df04943c83
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by double tapping on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppDoubleTapAppHeaderLandscape : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ trigger = MaximizeDesktopAppTrigger.DOUBLE_TAP_APP_HEADER
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt
new file mode 100644
index 000000000000..977846eac782
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by double tapping on the app header in portrait mode.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppDoubleTapAppHeaderPortrait : MaximizeAppWindow(
+ rotation = ROTATION_0,
+ trigger = MaximizeDesktopAppTrigger.DOUBLE_TAP_APP_HEADER
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt
new file mode 100644
index 000000000000..70319262e1d7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by tapping on the maximize button within the app header maximize menu.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppViaHeaderMenuLandscape : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ trigger = MaximizeDesktopAppTrigger.MAXIMIZE_BUTTON_IN_MENU
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt
new file mode 100644
index 000000000000..4c7eb8d210f8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by tapping on the maximize button within the app header maximize menu.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppViaHeaderMenuPortrait : MaximizeAppWindow(
+ rotation = ROTATION_0,
+ trigger = MaximizeDesktopAppTrigger.MAXIMIZE_BUTTON_IN_MENU
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt
index b399e9b52696..3f8a28f6e101 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt
@@ -23,6 +23,7 @@ import android.tools.flicker.annotation.FlickerConfigProvider
import android.tools.flicker.config.FlickerConfig
import android.tools.flicker.config.FlickerServiceConfig
import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
import com.android.wm.shell.scenarios.MaximizeAppWindow
import org.junit.Test
@@ -35,7 +36,10 @@ import org.junit.runner.RunWith
* stable display bounds.
*/
@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class MaximizeAppWithKeyboard : MaximizeAppWindow(rotation = ROTATION_90, usingKeyboard = true) {
+class MaximizeAppWithKeyboard : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ trigger = MaximizeDesktopAppTrigger.KEYBOARD_SHORTCUT
+) {
@ExpectedScenarios(["MAXIMIZE_APP"])
@Test
override fun maximizeAppWindow() = super.maximizeAppWindow()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt
new file mode 100644
index 000000000000..9cbc46b10ed6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopFromKeyboardShortcut {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val simpleAppHelper = SimpleAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(simpleAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ }
+
+ @Test
+ open fun enterDesktopFromKeyboardShortcut() {
+ simpleAppHelper.launchViaIntent(wmHelper)
+ testApp.enterDesktopModeViaKeyboard(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt
new file mode 100644
index 000000000000..1b1f1cfca6c8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ExitDesktopFromKeyboardShortcut {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val simpleAppHelper = SimpleAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(simpleAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ simpleAppHelper.launchViaIntent(wmHelper)
+ testApp.enterDesktopMode(wmHelper, device)
+ }
+
+ @Test
+ open fun exitDesktopToFullScreenFromKeyboardShortcut() {
+ testApp.exitDesktopModeToFullScreenViaKeyboard(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index 7855698d0151..92fe40d96fd7 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -26,6 +26,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
@@ -38,11 +39,10 @@ import org.junit.Rule
import org.junit.Test
@Ignore("Test Base Class")
-abstract class MaximizeAppWindow
-constructor(
+abstract class MaximizeAppWindow(
private val rotation: Rotation = Rotation.ROTATION_0,
isResizable: Boolean = true,
- private val usingKeyboard: Boolean = false
+ private val trigger: MaximizeDesktopAppTrigger = MaximizeDesktopAppTrigger.MAXIMIZE_MENU,
) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
@@ -59,7 +59,7 @@ constructor(
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- if (usingKeyboard) {
+ if (trigger == MaximizeDesktopAppTrigger.KEYBOARD_SHORTCUT) {
Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue)
}
tapl.setEnableRotation(true)
@@ -70,7 +70,7 @@ constructor(
@Test
open fun maximizeAppWindow() {
- testApp.maximiseDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
+ testApp.maximiseDesktopApp(wmHelper, device, trigger)
}
@After
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt
index 3e98e4342b3f..7d9f2bf8fdf6 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.scenarios
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.device.apphelpers.BrowserAppHelper
+import android.tools.device.apphelpers.GmailAppHelper
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -44,8 +44,8 @@ abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATI
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- private val browserHelper = BrowserAppHelper(instrumentation)
- private val browserApp = DesktopModeAppHelper(browserHelper)
+ private val gmailHelper = GmailAppHelper(instrumentation)
+ private val gmailApp = DesktopModeAppHelper(gmailHelper)
@Rule
@JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@@ -59,20 +59,20 @@ abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATI
ChangeDisplayOrientationRule.setRotation(rotation)
testApp.enterDesktopMode(wmHelper, device)
tapl.showTaskbarIfHidden()
- browserApp.launchViaIntent(wmHelper)
- browserApp.minimizeDesktopApp(wmHelper, device)
+ gmailApp.launchViaIntent(wmHelper)
+ gmailApp.minimizeDesktopApp(wmHelper, device)
}
@Test
open fun unminimizeApp() {
tapl.launchedAppState.taskbar
- .getAppIcon(browserHelper.appName)
- .launch(browserHelper.packageName)
+ .getAppIcon(gmailHelper.appName)
+ .launch(gmailHelper.packageName)
}
@After
fun teardown() {
testApp.exit(wmHelper)
- browserApp.exit(wmHelper)
+ gmailApp.exit(wmHelper)
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
index 1de47df78853..e51447f5cfcb 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -50,6 +50,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 6a6aa1abc9f3..fa9864b539ee 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -17,15 +17,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
import android.tools.NavBar
-import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.UiDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils.isLeftRightSplit
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -37,6 +37,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
+ private val device = UiDevice.getInstance(instrumentation)
+
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
@@ -73,7 +75,8 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy
}
?: return@add false
- if (isLandscape(flicker.scenario.endRotation)) {
+ if (isLeftRightSplit(instrumentation.context, flicker.scenario.endRotation,
+ device.displaySizeDp)) {
return@add if (flicker.scenario.isTablet) {
secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
} else {
@@ -109,7 +112,8 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy
val secondaryVisibleRegion =
secondaryAppLayer.visibleRegion?.bounds ?: return@add false
- if (isLandscape(flicker.scenario.endRotation)) {
+ if (isLeftRightSplit(instrumentation.context, flicker.scenario.endRotation,
+ device.displaySizeDp)) {
return@add if (flicker.scenario.isTablet) {
secondaryVisibleRegion.right <= primaryVisibleRegion.left
} else {
@@ -126,11 +130,6 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy
.waitForAndVerify()
}
- private fun isLandscape(rotation: Rotation): Boolean {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width() > displayBounds.height()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 34d001c858f6..7659ec903480 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -50,6 +50,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index 34d001c858f6..7659ec903480 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -50,6 +50,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
index 26203d4afccd..3fd93d3eaf59 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -17,16 +17,16 @@
package com.android.wm.shell.scenarios
import android.app.Instrumentation
-import android.graphics.Point
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils.isLeftRightSplit
+import com.android.wm.shell.flicker.utils.SplitScreenUtils.isTablet
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -89,14 +89,14 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
}
?: return@add false
- if (isLandscape(rotation)) {
- return@add if (isTablet()) {
+ if (isLeftRightSplit(instrumentation.context, rotation, device.displaySizeDp)) {
+ return@add if (isTablet(device.displaySizeDp)) {
secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
} else {
primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
}
} else {
- return@add if (isTablet()) {
+ return@add if (isTablet(device.displaySizeDp)) {
primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
} else {
primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
@@ -125,14 +125,14 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
val secondaryVisibleRegion =
secondaryAppLayer.visibleRegion?.bounds ?: return@add false
- if (isLandscape(rotation)) {
- return@add if (isTablet()) {
+ if (isLeftRightSplit(instrumentation.context, rotation, device.displaySizeDp)) {
+ return@add if (isTablet(device.displaySizeDp)) {
secondaryVisibleRegion.right <= primaryVisibleRegion.left
} else {
primaryVisibleRegion.right <= secondaryVisibleRegion.left
}
} else {
- return@add if (isTablet()) {
+ return@add if (isTablet(device.displaySizeDp)) {
primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
} else {
primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
@@ -141,15 +141,4 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
}
.waitForAndVerify()
}
-
- private fun isLandscape(rotation: Rotation): Boolean {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width() > displayBounds.height()
- }
-
- private fun isTablet(): Boolean {
- val sizeDp: Point = device.displaySizeDp
- val LARGE_SCREEN_DP_THRESHOLD = 600
- return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
- }
}
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
new file mode 100644
index 000000000000..68f7ef09ee70
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell
+
+import android.graphics.Point
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DisplayListener
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A TestRule to manage multiple simulated connected overlay displays.
+ */
+class SimulatedConnectedDisplayTestRule : TestRule {
+
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
+ private val displayManager = context.getSystemService(DisplayManager::class.java)
+ private val addedDisplays = mutableListOf<Int>()
+
+ override fun apply(base: Statement, description: Description): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ try {
+ base.evaluate()
+ } finally {
+ teardown()
+ }
+ }
+ }
+
+ private fun teardown() {
+ cleanupTestDisplays()
+ }
+
+ /**
+ * Adds multiple overlay displays with specified dimensions. Any existing overlay displays
+ * will be removed before adding the new ones.
+ *
+ * @param displays A list of [Point] objects, where each [Point] represents the
+ * width and height of a simulated display.
+ * @return List of displayIds of added displays.
+ */
+ fun setupTestDisplays(displays: List<Point>): List<Int> = runBlocking {
+ // Cleanup any existing overlay displays.
+ cleanupTestDisplays()
+
+ if (displays.isEmpty()) {
+ Log.w(TAG, "setupTestDisplays called with an empty list. No displays created.")
+ return@runBlocking emptyList()
+ }
+
+ val displayAddedFlow: Flow<Int> = callbackFlow {
+ val listener = object : DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {
+ trySend(displayId)
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {}
+ override fun onDisplayChanged(displayId: Int) {}
+ }
+
+ val handler = Handler(Looper.getMainLooper())
+ displayManager.registerDisplayListener(listener, handler)
+
+ awaitClose {
+ displayManager.unregisterDisplayListener(listener)
+ }
+ }
+
+ val displaySettings = displays.joinToString(separator = ";") { size ->
+ "${size.x}x${size.y}/$DEFAULT_DENSITY"
+ }
+
+ // Add the overlay displays
+ Settings.Global.putString(
+ InstrumentationRegistry.getInstrumentation().context.contentResolver,
+ Settings.Global.OVERLAY_DISPLAY_DEVICES, displaySettings
+ )
+ withTimeoutOrNull(TIMEOUT) {
+ displayAddedFlow.take(displays.size).collect { displayId ->
+ addedDisplays.add(displayId)
+ }
+ } ?: error("Timed out waiting for displays to be added.")
+ addedDisplays
+ }
+
+ /**
+ * Adds multiple overlay displays with default dimensions. Any existing overlay displays
+ * will be removed before adding the new ones.
+ *
+ * @param count number of displays to add.
+ * @return List of displayIds of added displays.
+ */
+ fun setupTestDisplays(count: Int): List<Int> {
+ val displays = List(count) { Point(DEFAULT_WIDTH, DEFAULT_HEIGHT) }
+ return setupTestDisplays(displays)
+ }
+
+ private fun cleanupTestDisplays() = runBlocking {
+ if (addedDisplays.isEmpty()) {
+ return@runBlocking
+ }
+
+ val displayRemovedFlow: Flow<Int> = callbackFlow {
+ val listener = object : DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {}
+ override fun onDisplayRemoved(displayId: Int) {
+ trySend(displayId)
+ }
+
+ override fun onDisplayChanged(displayId: Int) {}
+ }
+ val handler = Handler(Looper.getMainLooper())
+ displayManager.registerDisplayListener(listener, handler)
+
+ awaitClose {
+ displayManager.unregisterDisplayListener(listener)
+ }
+ }
+
+ // Remove overlay displays
+ Settings.Global.putString(
+ InstrumentationRegistry.getInstrumentation().context.contentResolver,
+ Settings.Global.OVERLAY_DISPLAY_DEVICES, null)
+
+ withTimeoutOrNull(TIMEOUT) {
+ displayRemovedFlow.take(addedDisplays.size).collect { displayId ->
+ addedDisplays.remove(displayId)
+ }
+ } ?: error("Timed out waiting for displays to be removed.")
+ }
+
+ private companion object {
+ const val DEFAULT_WIDTH = 1280
+ const val DEFAULT_HEIGHT = 720
+ const val DEFAULT_DENSITY = 160
+ const val TAG = "SimulatedConnectedDisplayTestRule"
+ val TIMEOUT = 10.seconds
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index 9c1a8f17aeee..a4ecac9dfeb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -50,6 +50,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index ae73dae99d6f..75ffdc69c73b 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -50,6 +50,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index a136936c0838..8003cbaada50 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -50,6 +50,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index feb3edc9dab7..49d6877a1654 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -17,12 +17,14 @@
package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
+import android.content.Context
import android.graphics.Point
import android.os.SystemClock
import android.tools.Rotation
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.component.IComponentNameMatcher
@@ -393,4 +395,24 @@ object SplitScreenUtils {
error("Fail to copy content in split")
}
}
+
+ fun isLeftRightSplit(context: Context, rotation: Rotation, displaySizeDp: Point): Boolean {
+ val allowLeftRightSplit = context.resources.getBoolean(
+ com.android.internal.R.bool.config_leftRightSplitInPortrait)
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ val isLandscape = displayBounds.width() > displayBounds.height()
+ if (allowLeftRightSplit && isTablet(displaySizeDp)) {
+ // Certain devices allow left/right split in portrait, so they end up with top/bottom
+ // split in landscape
+ return !isLandscape
+ } else {
+ return isLandscape
+ }
+ }
+
+ fun isTablet(displaySizeDp: Point): Boolean {
+ val LARGE_SCREEN_DP_THRESHOLD = 600
+ return displaySizeDp.x >= LARGE_SCREEN_DP_THRESHOLD
+ && displaySizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+ }
}
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 05750a54f566..b139c000a1a9 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
@@ -889,8 +889,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
*/
private void doStartEvents(int startX, int moveX) {
doMotionEvent(MotionEvent.ACTION_DOWN, startX);
- mController.onThresholdCrossed();
doMotionEvent(MotionEvent.ACTION_MOVE, moveX);
+ mController.onThresholdCrossed();
}
private void simulateRemoteAnimationStart() throws RemoteException {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 2ef6c558b0b5..43bcc3b61124 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -58,8 +58,7 @@ public class BackProgressAnimatorTest extends ShellTestCase {
/* frameTime = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
- /* swipeEdge = */ BackEvent.EDGE_LEFT,
- /* departingAnimationTarget = */ null);
+ /* swipeEdge = */ BackEvent.EDGE_LEFT);
}
@Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 2cc52c5ab9ad..9d4cc49a7a65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -224,8 +224,7 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() {
/* frameTime = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
- /* swipeEdge = */ BackEvent.EDGE_LEFT,
- /* departingAnimationTarget = */ null
+ /* swipeEdge = */ BackEvent.EDGE_LEFT
)
private fun createAnimationTarget(open: Boolean): RemoteAnimationTarget {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 7a7d88b80ce3..b3d2db6da6d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -804,7 +803,7 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleData.setListener(mListener);
changeExpandedStateAtTime(true, 2000L);
- verifyZeroInteractions(mListener);
+ verifyNoMoreInteractions(mListener);
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index 42310caba1c6..b136bed3c942 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
@@ -25,6 +26,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -35,6 +37,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -50,6 +53,7 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestSyncExecutor;
+import com.android.wm.shell.bubbles.BubbleTransitions.DraggedBubbleIconToFullscreen;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.ShellExecutor;
@@ -134,17 +138,19 @@ public class BubbleTransitionsTest extends ShellTestCase {
when(tvtc.getTaskInfo()).thenReturn(taskInfo);
when(tv.getController()).thenReturn(tvtc);
when(mBubble.getTaskView()).thenReturn(tv);
+ when(tv.getTaskInfo()).thenReturn(taskInfo);
mRepository.add(tvtc);
return taskInfo;
}
- private TransitionInfo setupFullscreenTaskTransition(ActivityManager.RunningTaskInfo taskInfo) {
+ private TransitionInfo setupFullscreenTaskTransition(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskLeash, SurfaceControl snapshot) {
final TransitionInfo info = new TransitionInfo(TRANSIT_CONVERT_TO_BUBBLE, 0);
- final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
- mock(SurfaceControl.class));
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, taskLeash);
chg.setTaskInfo(taskInfo);
chg.setMode(TRANSIT_CHANGE);
chg.setStartAbsBounds(new Rect(0, 0, FULLSCREEN_TASK_WIDTH, FULLSCREEN_TASK_HEIGHT));
+ chg.setSnapshot(snapshot, /* luma= */ 0f);
info.addChange(chg);
info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
return info;
@@ -172,7 +178,9 @@ public class BubbleTransitionsTest extends ShellTestCase {
// Ensure we are communicating with the taskviewtransitions queue
assertTrue(mTaskViewTransitions.hasPending());
- final TransitionInfo info = setupFullscreenTaskTransition(taskInfo);
+ SurfaceControl taskLeash = new SurfaceControl.Builder().setName("taskLeash").build();
+ SurfaceControl snapshot = new SurfaceControl.Builder().setName("snapshot").build();
+ final TransitionInfo info = setupFullscreenTaskTransition(taskInfo, taskLeash, snapshot);
SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
final boolean[] finishCalled = new boolean[]{false};
@@ -183,7 +191,8 @@ public class BubbleTransitionsTest extends ShellTestCase {
ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb);
assertFalse(mTaskViewTransitions.hasPending());
- verify(startT).setPosition(any(), eq(0f), eq(0f));
+ verify(startT).setPosition(taskLeash, 0, 0);
+ verify(startT).setPosition(snapshot, 0, 0);
verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean());
@@ -194,7 +203,7 @@ public class BubbleTransitionsTest extends ShellTestCase {
// Check that preparing transition is not reset before continueExpand is called
verify(mBubble, never()).setPreparingTransition(any());
ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class);
- verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture());
+ verify(mLayerView).animateConvert(any(), any(), anyFloat(), any(), any(), animCb.capture());
// continueExpand is now called, check that preparing transition is cleared
ctb.continueExpand();
@@ -209,14 +218,14 @@ public class BubbleTransitionsTest extends ShellTestCase {
public void testConvertToBubble_drag() {
ActivityManager.RunningTaskInfo taskInfo = setupBubble();
- Rect draggedTaskBounds = new Rect(10, 20, 30, 40);
WindowContainerTransaction pendingWct = new WindowContainerTransaction();
WindowContainerToken pendingDragOpToken = createMockToken();
pendingWct.reorder(pendingDragOpToken, /* onTop= */ false);
+ PointF dragPosition = new PointF(10f, 20f);
BubbleTransitions.DragData dragData = new BubbleTransitions.DragData(
- draggedTaskBounds, pendingWct, /* releasedOnLeft= */ false
- );
+ /* releasedOnLeft= */ false, /* taskScale= */ 0.5f, /* cornerRadius= */ 10f,
+ dragPosition, pendingWct);
final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble(
mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner,
@@ -234,15 +243,21 @@ public class BubbleTransitionsTest extends ShellTestCase {
== WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
&& op.getContainer() == pendingDragOpToken.asBinder())).isTrue();
- final TransitionInfo info = setupFullscreenTaskTransition(taskInfo);
+ SurfaceControl taskLeash = new SurfaceControl.Builder().setName("taskLeash").build();
+ SurfaceControl snapshot = new SurfaceControl.Builder().setName("snapshot").build();
+ final TransitionInfo info = setupFullscreenTaskTransition(taskInfo, taskLeash, snapshot);
SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
Transitions.TransitionFinishCallback finishCb = wct -> {};
ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb);
- // Verify that dragged task bounds are used for the position
- verify(startT).setPosition(any(), eq((float) draggedTaskBounds.left),
- eq((float) draggedTaskBounds.top));
+ // Verify that snapshot and task are placed at where the drag ended
+ verify(startT).setPosition(taskLeash, dragPosition.x, dragPosition.y);
+ verify(startT).setPosition(snapshot, dragPosition.x, dragPosition.y);
+ // Snapshot has the scale of the dragged task
+ verify(startT).setScale(snapshot, dragData.getTaskScale(), dragData.getTaskScale());
+ // Snapshot has dragged task corner radius
+ verify(startT).setCornerRadius(snapshot, dragData.getCornerRadius());
}
@Test
@@ -273,4 +288,62 @@ public class BubbleTransitionsTest extends ShellTestCase {
// directly tested.
assertFalse(mTaskViewTransitions.hasPending());
}
+
+ @Test
+ public void convertDraggedBubbleToFullscreen() {
+ ActivityManager.RunningTaskInfo taskInfo = setupBubble();
+ final DraggedBubbleIconToFullscreen bt =
+ (DraggedBubbleIconToFullscreen) mBubbleTransitions
+ .startDraggedBubbleIconToFullscreen(mBubble);
+ verify(mTransitions).startTransition(anyInt(), any(), eq(bt));
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
+ mock(SurfaceControl.class));
+ chg.setMode(TRANSIT_TO_FRONT);
+ chg.setTaskInfo(taskInfo);
+ info.addChange(chg);
+ info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ Transitions.TransitionFinishCallback finishCb = wct -> {};
+ bt.startAnimation(bt.mTransition, info, startT, finishT, finishCb);
+ verify(startT).apply();
+ assertFalse(mTaskViewTransitions.hasPending());
+ }
+
+ @Test
+ public void convertFloatingBubbleToFullscreen() {
+ final BubbleExpandedView bev = mock(BubbleExpandedView.class);
+ final ViewRootImpl vri = mock(ViewRootImpl.class);
+ when(bev.getViewRootImpl()).thenReturn(vri);
+ when(mBubble.getBubbleBarExpandedView()).thenReturn(null);
+ when(mBubble.getExpandedView()).thenReturn(bev);
+
+ ActivityManager.RunningTaskInfo taskInfo = setupBubble();
+ final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertFromBubble(
+ mBubble, taskInfo);
+ final BubbleTransitions.ConvertFromBubble cfb = (BubbleTransitions.ConvertFromBubble) bt;
+ verify(mTransitions).startTransition(anyInt(), any(), eq(cfb));
+ verify(mBubble).setPreparingTransition(eq(bt));
+ assertTrue(mTaskViewTransitions.hasPending());
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0);
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
+ mock(SurfaceControl.class));
+ chg.setMode(TRANSIT_CHANGE);
+ chg.setTaskInfo(taskInfo);
+ info.addChange(chg);
+ info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ Transitions.TransitionFinishCallback finishCb = wct -> {};
+ cfb.startAnimation(cfb.mTransition, info, startT, finishT, finishCb);
+
+ // Can really only verify that it interfaces with the taskViewTransitions queue.
+ // The actual functioning of this is tightly-coupled with SurfaceFlinger and renderthread
+ // in order to properly synchronize surface manipulation with drawing and thus can't be
+ // directly tested.
+ assertFalse(mTaskViewTransitions.hasPending());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
index c4b9c9ba43f1..b4b96791298d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
@@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.floatThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
@@ -84,7 +83,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onMove(0, -600);
// Check that velocity up is about 5000
verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000));
- verifyZeroInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mMotionEventListener);
verify(mInterceptTouchRunnable).run();
}
@@ -94,8 +93,8 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100));
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100));
- verifyZeroInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
@Test
@@ -107,7 +106,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verify(mMotionEventListener).onMove(100, 0);
verify(mMotionEventListener).onUp(0, 0);
- verifyZeroInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mMotionEventListener);
verify(mInterceptTouchRunnable).run();
}
@@ -119,7 +118,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verifyNoMoreInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
@Test
@@ -129,7 +128,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verify(mMotionEventListener).onCancel();
verifyNoMoreInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
private MotionEvent newEvent(int actionDown, float x, float y) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
index 3323740697f3..1472464e8143 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
@@ -105,6 +105,6 @@ public class DevicePostureControllerTest extends ShellTestCase {
int sameDevicePosture = mDevicePostureCaptor.getValue();
mDevicePostureController.onDevicePostureChanged(sameDevicePosture);
- verifyZeroInteractions(mOnDevicePostureChangedListener);
+ verifyNoMoreInteractions(mOnDevicePostureChangedListener);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index ee9d17706372..a53277a3764e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -32,7 +32,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.graphics.Insets;
import android.graphics.Point;
@@ -108,26 +108,26 @@ public class DisplayImeControllerTest extends ShellTestCase {
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void insetsChanged_schedulesNoWorkOnExecutor() {
Looper.prepare();
mPerDisplay.insetsChanged(insetsStateWithIme(false));
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
// With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore
@@ -135,7 +135,7 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void reappliesVisibilityToChangedLeash() {
- verifyZeroInteractions(mT);
+ verifyNoMoreInteractions(mT);
mPerDisplay.mImeShowing = false;
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
index 96d202ce3a85..7d1866975848 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -145,7 +145,7 @@ public class TabletopModeControllerTest extends ShellTestCase {
mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
- verifyZeroInteractions(mOnTabletopModeChangedListener);
+ verifyNoMoreInteractions(mOnTabletopModeChangedListener);
}
// Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
index e92e243172f7..a2066dbf7a5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
@@ -157,7 +157,7 @@ public class PipAppOpsListenerTest {
opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
packageName);
- verifyZeroInteractions(mMockExecutor);
+ verifyNoMoreInteractions(mMockExecutor);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java
index 01b76edd9b25..1066276becc7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.common.pip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -128,6 +130,31 @@ public class PipBoundsStateTest extends ShellTestCase {
}
@Test
+ public void setLastPipComponentName_notChanged_doesNotCallbackComponentChangedListener() {
+ mPipBoundsState.setLastPipComponentName(mTestComponentName1);
+ PipBoundsState.OnPipComponentChangedListener mockListener =
+ mock(PipBoundsState.OnPipComponentChangedListener.class);
+
+ mPipBoundsState.addOnPipComponentChangedListener(mockListener);
+ mPipBoundsState.setLastPipComponentName(mTestComponentName1);
+
+ verify(mockListener, never()).onPipComponentChanged(any(), any());
+ }
+
+ @Test
+ public void setLastPipComponentName_changed_callbackComponentChangedListener() {
+ mPipBoundsState.setLastPipComponentName(mTestComponentName1);
+ PipBoundsState.OnPipComponentChangedListener mockListener =
+ mock(PipBoundsState.OnPipComponentChangedListener.class);
+
+ mPipBoundsState.addOnPipComponentChangedListener(mockListener);
+ mPipBoundsState.setLastPipComponentName(mTestComponentName2);
+
+ verify(mockListener).onPipComponentChanged(
+ eq(mTestComponentName1), eq(mTestComponentName2));
+ }
+
+ @Test
public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
index e85d30fbaebd..25dbc64f83de 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -41,7 +42,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import org.junit.Before;
import org.junit.Test;
@@ -62,11 +63,12 @@ import java.util.Optional;
public class PipDesktopStateTest {
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
- @Mock private Optional<DesktopWallpaperActivityTokenProvider>
- mMockDesktopWallpaperActivityTokenProviderOptional;
@Mock private DesktopUserRepositories mMockDesktopUserRepositories;
- @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
@Mock private DesktopRepository mMockDesktopRepository;
+ @Mock
+ private Optional<DragToDesktopTransitionHandler> mMockDragToDesktopTransitionHandlerOptional;
+ @Mock private DragToDesktopTransitionHandler mMockDragToDesktopTransitionHandler;
+
@Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
@Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;
@@ -78,11 +80,12 @@ public class PipDesktopStateTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
- when(mMockDesktopWallpaperActivityTokenProviderOptional.get()).thenReturn(
- mMockDesktopWallpaperActivityTokenProvider);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);
- when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(true);
+
+ when(mMockDragToDesktopTransitionHandlerOptional.get()).thenReturn(
+ mMockDragToDesktopTransitionHandler);
+ when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(true);
when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);
@@ -93,7 +96,7 @@ public class PipDesktopStateTest {
mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
mMockDesktopUserRepositoriesOptional,
- mMockDesktopWallpaperActivityTokenProviderOptional,
+ mMockDragToDesktopTransitionHandlerOptional,
mMockRootTaskDisplayAreaOrganizer);
}
@@ -110,72 +113,36 @@ public class PipDesktopStateTest {
}
@Test
- public void isDesktopWindowingPipEnabled_desktopWallpaperEmpty_returnsFalse() {
- when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(false);
+ public void isDesktopWindowingPipEnabled_dragToDesktopTransitionHandlerEmpty_returnsFalse() {
+ when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(false);
assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
}
@Test
- @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_PIP)
+ @EnableFlags({
+ FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, FLAG_ENABLE_PIP2
+ })
public void isConnectedDisplaysPipEnabled_returnsTrue() {
assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
}
@Test
- public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipPresent_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(true);
-
- assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
- }
-
- @Test
- public void isPipEnteringInDesktopMode_visibleCountNonzero_minimizedPipAbsent_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
-
- assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
- }
-
- @Test
- public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipAbsent_returnsFalse() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
-
- assertFalse(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
- }
-
- @Test
- public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperInvisible_returnsFalse() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(false);
-
- assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
- }
-
- @Test
- public void shouldExitPipExitDesktopMode_visibleCountNonzero_wallpaperVisible_returnsFalse() {
+ public void isPipInDesktopMode_anyDeskActive_returnsTrue() {
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(true);
- assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ assertTrue(mPipDesktopState.isPipInDesktopMode());
}
@Test
- public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperVisible_returnsTrue() {
+ public void isPipInDesktopMode_noDeskActive_returnsFalse() {
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(true);
- assertTrue(mPipDesktopState.shouldExitPipExitDesktopMode());
+ assertFalse(mPipDesktopState.isPipInDesktopMode());
}
@Test
public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
- // Set visible task count to 1 so isPipExitingToDesktopMode returns true
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -184,7 +151,6 @@ public class PipDesktopStateTest {
@Test
public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
- // Set visible task count to 1 so isPipExitingToDesktopMode returns true
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -198,6 +164,20 @@ public class PipDesktopStateTest {
assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
}
+ @Test
+ public void isDragToDesktopInProgress_inProgress_returnsTrue() {
+ when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(true);
+
+ assertTrue(mPipDesktopState.isDragToDesktopInProgress());
+ }
+
+ @Test
+ public void isDragToDesktopInProgress_notInProgress_returnsFalse() {
+ when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDragToDesktopInProgress());
+ }
+
private void setDisplayWindowingMode(int windowingMode) {
mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
index 1756aad8fc9b..cc23d9a75fcd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDoubleTapHelperTest.java
@@ -21,7 +21,6 @@ import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAU
import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.graphics.Point;
@@ -41,131 +40,75 @@ import org.mockito.Mock;
*/
@RunWith(AndroidTestingRunner.class)
public class PipDoubleTapHelperTest extends ShellTestCase {
- // represents the current pip window state and has information on current
- // max, min, and normal sizes
- @Mock private PipBoundsState mBoundStateMock;
- // tied to boundsStateMock.getBounds() in setUp()
- @Mock private Rect mBoundsMock;
-
- // represents the most recent manually resized bounds
- // i.e. dimensions from the most recent pinch in/out
- @Mock private Rect mUserResizeBoundsMock;
-
- // actual dimensions of the pip screen bounds
- private static final int MAX_WIDTH = 100;
- private static final int DEFAULT_WIDTH = 40;
- private static final int MIN_WIDTH = 10;
-
- private static final int AVERAGE_WIDTH = (MAX_WIDTH + MIN_WIDTH) / 2;
-
- /**
- * Initializes mocks and assigns values for different pip screen bounds.
- */
+ @Mock private PipBoundsState mMockPipBoundsState;
+
+ // Actual dimension guidelines of the PiP bounds.
+ private static final int MAX_EDGE_SIZE = 100;
+ private static final int DEFAULT_EDGE_SIZE = 60;
+ private static final int MIN_EDGE_SIZE = 50;
+ private static final int AVERAGE_EDGE_SIZE = (MAX_EDGE_SIZE + MIN_EDGE_SIZE) / 2;
+
@Before
public void setUp() {
// define pip bounds
- when(mBoundStateMock.getMaxSize()).thenReturn(new Point(MAX_WIDTH, 20));
- when(mBoundStateMock.getMinSize()).thenReturn(new Point(MIN_WIDTH, 2));
+ when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_EDGE_SIZE, MAX_EDGE_SIZE));
+ when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(MIN_EDGE_SIZE, MIN_EDGE_SIZE));
- Rect rectMock = mock(Rect.class);
- when(rectMock.width()).thenReturn(DEFAULT_WIDTH);
- when(mBoundStateMock.getNormalBounds()).thenReturn(rectMock);
+ final Rect normalBounds = new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE);
+ when(mMockPipBoundsState.getNormalBounds()).thenReturn(normalBounds);
+ }
- when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
- when(mBoundStateMock.getBounds()).thenReturn(mBoundsMock);
+ @Test
+ public void nextSizeSpec_resizedWiderThanAverage_returnDefaultThenCustom() {
+ final int resizeEdgeSize = (MAX_EDGE_SIZE + AVERAGE_EDGE_SIZE) / 2;
+ final Rect userResizeBounds = new Rect(0, 0, resizeEdgeSize, resizeEdgeSize);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT);
+
+ // once we toggle to DEFAULT only PiP bounds state gets updated - not the user resize bounds
+ when(mMockPipBoundsState.getBounds()).thenReturn(
+ new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE));
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_CUSTOM);
+ }
+
+ @Test
+ public void nextSizeSpec_resizedSmallerThanAverage_returnMaxThenCustom() {
+ final int resizeEdgeSize = (AVERAGE_EDGE_SIZE + MIN_EDGE_SIZE) / 2;
+ final Rect userResizeBounds = new Rect(0, 0, resizeEdgeSize, resizeEdgeSize);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX);
+
+ // Once we toggle to MAX our screen size gets updated but not the user resize bounds
+ when(mMockPipBoundsState.getBounds()).thenReturn(
+ new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE));
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_CUSTOM);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to a larger than the average but not the maximum width,
- * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.DEFAULT}
- */
@Test
- public void testNextScreenSize_resizedWiderThanAverage_returnDefaultThenCustom() {
- // make the user resize width in between MAX and average
- when(mUserResizeBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
- // make current bounds same as resized bound since no double tap yet
- when(mBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to DEFAULT state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_DEFAULT);
-
- // once we toggle to DEFAULT our screen size gets updated
- // but not the user resize bounds
- when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to CUSTOM state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_CUSTOM);
+ public void nextSizeSpec_resizedToMax_returnDefault() {
+ final Rect userResizeBounds = new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to a smaller than the average but not the default width,
- * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.MAX}
- */
@Test
- public void testNextScreenSize_resizedNarrowerThanAverage_returnMaxThenCustom() {
- // make the user resize width in between MIN and average
- when(mUserResizeBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
- // make current bounds same as resized bound since no double tap yet
- when(mBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to MAX state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_MAX);
-
- // once we toggle to MAX our screen size gets updated
- // but not the user resize bounds
- when(mBoundsMock.width()).thenReturn(MAX_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to CUSTOM state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_CUSTOM);
+ public void nextSizeSpec_resizedToDefault_returnMax() {
+ final Rect userResizeBounds = new Rect(0, 0, DEFAULT_EDGE_SIZE, DEFAULT_EDGE_SIZE);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to exactly the maximum width
- * then we toggle to {@code PipSizeSpec.DEFAULT}
- */
@Test
- public void testNextScreenSize_resizedToMax_returnDefault() {
- // the resized width is the same as MAX_WIDTH
- when(mUserResizeBoundsMock.width()).thenReturn(MAX_WIDTH);
- // the current bounds are also at MAX_WIDTH
- when(mBoundsMock.width()).thenReturn(MAX_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to DEFAULT state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_DEFAULT);
+ public void nextSizeSpec_resizedToAlmostMax_returnDefault() {
+ final Rect userResizeBounds = new Rect(0, 0, MAX_EDGE_SIZE, MAX_EDGE_SIZE - 1);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_DEFAULT);
}
- /**
- * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
- *
- * <p>when the user resizes the screen to exactly the default width
- * then we toggle to {@code PipSizeSpec.MAX}
- */
@Test
- public void testNextScreenSize_resizedToDefault_returnMax() {
- // the resized width is the same as DEFAULT_WIDTH
- when(mUserResizeBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
- // the current bounds are also at DEFAULT_WIDTH
- when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
-
- // then nextScreenSize() i.e. double tapping should
- // toggle to MAX state
- Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
- SIZE_SPEC_MAX);
+ public void nextSizeSpec_resizedToAlmostMin_returnMax() {
+ final Rect userResizeBounds = new Rect(0, 0, MIN_EDGE_SIZE, MIN_EDGE_SIZE + 1);
+ when(mMockPipBoundsState.getBounds()).thenReturn(userResizeBounds);
+ Assert.assertEquals(nextSizeSpec(mMockPipBoundsState, userResizeBounds), SIZE_SPEC_MAX);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 598a101b8bcd..597e4a55ed0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -59,6 +59,7 @@ import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIRequests;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.sysui.ShellController;
@@ -738,6 +739,22 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mController, never()).removeLayouts(taskInfo.taskId);
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ public void testSendCompatUIRequest_createRestartDialog() {
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ false);
+ doReturn(true).when(mMockRestartDialogLayout)
+ .needsToBeRecreated(any(TaskInfo.class),
+ any(ShellTaskOrganizer.TaskListener.class));
+ doReturn(true).when(mCompatUIConfiguration).isRestartDialogEnabled();
+ doReturn(true).when(mCompatUIConfiguration).shouldShowRestartDialogAgain(eq(taskInfo));
+
+ mController.sendCompatUIRequest(new CompatUIRequests.DisplayCompatShowRestartDialog(
+ taskInfo, mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+ }
+
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
/* isFocused */ false, /* isTopActivityTransparent */ false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt
new file mode 100644
index 000000000000..5c77f78a7dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.crashhandling
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.PendingIntent
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.HomeIntentProvider
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.Rule
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class ShellCrashHandlerTest : ShellTestCase() {
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeStatus::class.java)
+ .mockStatic(PendingIntent::class.java)
+ .build()!!
+
+ private val testExecutor = mock<ShellExecutor>()
+ private val context = mock<Context>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+
+ private lateinit var homeIntentProvider: HomeIntentProvider
+ private lateinit var crashHandler: ShellCrashHandler
+ private lateinit var shellInit: ShellInit
+
+
+ @Before
+ fun setup() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+ whenever(PendingIntent.getActivity(any(), any(), any(), any(), any())).thenReturn(mock())
+
+ shellInit = spy(ShellInit(testExecutor))
+
+ homeIntentProvider = HomeIntentProvider(context)
+ crashHandler = ShellCrashHandler(context, shellTaskOrganizer, homeIntentProvider, shellInit)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun init_freeformTaskExists_sendsHomeIntent() {
+ val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(createTaskInfo(1)))
+
+ shellInit.init()
+
+ verify(shellTaskOrganizer).applyTransaction(
+ wctCaptor.capture()
+ )
+ wctCaptor.value.assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+ }
+
+ private fun launchHomeIntent(displayId: Int): Intent {
+ return Intent(Intent.ACTION_MAIN).apply {
+ if (displayId != DEFAULT_DISPLAY) {
+ addCategory(Intent.CATEGORY_SECONDARY_HOME)
+ } else {
+ addCategory(Intent.CATEGORY_HOME)
+ }
+ }
+ }
+
+ private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
+ RunningTaskInfo().apply {
+ taskId = id
+ displayId = DEFAULT_DISPLAY
+ configuration.windowConfiguration.windowingMode = windowingMode
+ token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
+ baseIntent = Intent().apply { component = ComponentName("package", "component.name") }
+ }
+
+ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) {
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ assertThat(op.activityIntent?.component).isEqualTo(intent.component)
+ assertThat(op.activityIntent?.categories).isEqualTo(intent.categories)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 70a30a3ca7a9..d58f8a34c98e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -188,7 +188,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
SCREEN_ORIENTATION_LANDSCAPE,
)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -209,7 +209,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
SCREEN_ORIENTATION_LANDSCAPE,
)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -225,7 +225,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
handler.handleActivityOrientationChange(task, newTask)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -240,7 +240,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
SCREEN_ORIENTATION_LANDSCAPE,
)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -318,7 +318,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
val arg: ArgumentCaptor<WindowContainerTransaction> =
ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(resizeTransitionHandler, atLeastOnce())
- .startTransition(capture(arg), eq(currentBounds))
+ .startTransition(capture(arg), eq(currentBounds), isNull())
return arg.value
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 8ad54f5a0bb4..2aebcdcc3bf5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -28,14 +28,22 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.whenever
@@ -46,6 +54,7 @@ import org.mockito.quality.Strictness
*
* Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
@@ -55,6 +64,8 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
@Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDesktopTasksController: DesktopTasksController
@Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController
+ private val desktopRepositoryInitializer = FakeDesktopRepositoryInitializer()
+ private val testScope = TestScope()
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
@@ -77,7 +88,9 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
DesktopDisplayEventHandler(
context,
shellInit,
+ testScope.backgroundScope,
displayController,
+ desktopRepositoryInitializer,
mockDesktopUserRepositories,
mockDesktopTasksController,
desktopDisplayModeController,
@@ -89,17 +102,58 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
@After
fun tearDown() {
+ testScope.cancel()
mockitoSession.finishMocking()
}
@Test
- fun testDisplayAdded_supportsDesks_createsDesk() {
- whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+ fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_createsDesk() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
- onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
+ runCurrent()
- verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
- }
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ runCurrent()
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_supportsDesks_desktopRepositoryInitializedTwice_createsDeskOnce() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
+ runCurrent()
+
+ verify(mockDesktopTasksController, times(1)).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
+ runCurrent()
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
@Test
fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() {
@@ -141,4 +195,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
verify(desktopDisplayModeController).refreshDisplayWindowingMode()
}
+
+ private class FakeDesktopRepositoryInitializer : DesktopRepositoryInitializer {
+ override var deskRecreationFactory: DesktopRepositoryInitializer.DeskRecreationFactory =
+ DesktopRepositoryInitializer.DeskRecreationFactory { _, _, deskId -> deskId }
+
+ override val isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ override fun initialize(userRepositories: DesktopUserRepositories) {
+ isInitialized.value = true
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
index cc37c440f650..105941079095 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -21,29 +21,42 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ContentResolver
+import android.hardware.input.InputManager
import android.os.Binder
+import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.UsesFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DisplayAreaInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.server.display.feature.flags.Flags as DisplayFlags
import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
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.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -56,6 +69,7 @@ import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/**
* Test class for [DesktopDisplayModeController]
@@ -64,13 +78,20 @@ import org.mockito.kotlin.whenever
*/
@SmallTest
@RunWith(TestParameterInjector::class)
-class DesktopDisplayModeControllerTest : ShellTestCase() {
+@UsesFlags(com.android.server.display.feature.flags.Flags::class)
+class DesktopDisplayModeControllerTest(
+ @TestParameter(valuesProvider = FlagsParameterizationProvider::class)
+ flags: FlagsParameterization
+) : ShellTestCase() {
private val transitions = mock<Transitions>()
private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
private val mockWindowManager = mock<IWindowManager>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
+ private val inputManager = mock<InputManager>()
+ private val displayController = mock<DisplayController>()
+ private val mainHandler = mock<Handler>()
private lateinit var controller: DesktopDisplayModeController
@@ -81,9 +102,26 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
private val wallpaperToken = MockToken().token()
+ private val externalDisplay = mock<Display>()
+
+ private lateinit var extendedDisplaySettingsRestoreSession:
+ ExtendedDisplaySettingsRestoreSession
+
+ private lateinit var mockitoSession: StaticMockitoSession
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ extendedDisplaySettingsRestoreSession =
+ ExtendedDisplaySettingsRestoreSession(context.contentResolver)
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.thenReturn(defaultTDA)
@@ -95,74 +133,104 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
mockWindowManager,
shellTaskOrganizer,
desktopWallpaperActivityTokenProvider,
+ inputManager,
+ displayController,
+ mainHandler,
)
runningTasks.add(freeformTask)
runningTasks.add(fullscreenTask)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks))
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay)
+ setTabletModeStatus(SwitchState.UNKNOWN)
}
- private fun testDisplayWindowingModeSwitch(
- defaultWindowingMode: Int,
- extendedDisplayEnabled: Boolean,
- expectToSwitch: Boolean,
- ) {
- defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode)
- val settingsSession =
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (extendedDisplayEnabled) 1 else 0,
- )
+ @After
+ fun tearDown() {
+ extendedDisplaySettingsRestoreSession.restore()
+ mockitoSession.finishMocking()
+ }
- settingsSession.use {
- connectExternalDisplay()
- if (expectToSwitch) {
- // Assumes [connectExternalDisplay] properly triggered the switching transition.
- // Will verify the transition later along with [disconnectExternalDisplay].
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- }
- disconnectExternalDisplay()
+ private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) {
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+ setExtendedMode(true)
- if (expectToSwitch) {
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(2))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(defaultWindowingMode)
- assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- } else {
- verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
- }
+ connectExternalDisplay()
+ if (expectToSwitch) {
+ // Assumes [connectExternalDisplay] properly triggered the switching transition.
+ // Will verify the transition later along with [disconnectExternalDisplay].
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ }
+ disconnectExternalDisplay()
+
+ if (expectToSwitch) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
}
}
@Test
@DisableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
- fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled(
- @TestParameter param: ModeSwitchTestCase
- ) {
- testDisplayWindowingModeSwitch(
- param.defaultWindowingMode,
- param.extendedDisplayEnabled,
- // When the flag is disabled, never switch.
- expectToSwitch = false,
- )
+ fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled() {
+ // When the flag is disabled, never switch.
+ testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ false)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
- fun displayWindowingModeSwitchOnDisplayConnected(@TestParameter param: ModeSwitchTestCase) {
- testDisplayWindowingModeSwitch(
- param.defaultWindowingMode,
- param.extendedDisplayEnabled,
- param.expectToSwitchByDefault,
- )
+ fun displayWindowingModeSwitchOnDisplayConnected() {
+ testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
+ @DisableFlags(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH)
+ fun testTargetWindowingMode_formfactorDisabled(
+ @TestParameter param: ExternalDisplayBasedTargetModeTestCase,
+ @TestParameter tabletModeStatus: SwitchState,
+ ) {
+ whenever(mockWindowManager.getWindowingMode(anyInt()))
+ .thenReturn(param.defaultWindowingMode)
+ if (param.hasExternalDisplay) {
+ connectExternalDisplay()
+ } else {
+ disconnectExternalDisplay()
+ }
+ setTabletModeStatus(tabletModeStatus)
+ setExtendedMode(param.extendedDisplayEnabled)
+
+ assertThat(controller.getTargetWindowingModeForDefaultDisplay())
+ .isEqualTo(param.expectedWindowingMode)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING,
+ Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH,
+ )
+ fun testTargetWindowingMode(@TestParameter param: FormFactorBasedTargetModeTestCase) {
+ if (param.hasExternalDisplay) {
+ connectExternalDisplay()
+ } else {
+ disconnectExternalDisplay()
+ }
+ setTabletModeStatus(param.tabletModeStatus)
+ setExtendedMode(param.extendedDisplayEnabled)
+
+ assertThat(controller.getTargetWindowingModeForDefaultDisplay())
+ .isEqualTo(param.expectedWindowingMode)
}
@Test
@@ -170,18 +238,16 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
fun displayWindowingModeSwitch_existingTasksOnConnected() {
defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+ setExtendedMode(true)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
+ connectExternalDisplay()
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- }
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
@@ -191,18 +257,16 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
WINDOWING_MODE_FULLSCREEN
}
+ setExtendedMode(true)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- disconnectExternalDisplay()
+ disconnectExternalDisplay()
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
}
private fun connectExternalDisplay() {
@@ -217,49 +281,188 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
controller.refreshDisplayWindowingMode()
}
- private class ExtendedDisplaySettingsSession(
- private val contentResolver: ContentResolver,
- private val overrideValue: Int,
- ) : AutoCloseable {
+ private fun setTabletModeStatus(status: SwitchState) {
+ whenever(inputManager.isInTabletMode()).thenReturn(status.value)
+ }
+
+ private fun setExtendedMode(enabled: Boolean) {
+ if (DisplayFlags.enableDisplayContentModeManagement()) {
+ doReturn(enabled).`when` {
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, externalDisplay)
+ }
+ } else {
+ Settings.Global.putInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ if (enabled) 1 else 0,
+ )
+ }
+ }
+
+ private class ExtendedDisplaySettingsRestoreSession(
+ private val contentResolver: ContentResolver
+ ) {
private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
- init {
- Settings.Global.putInt(contentResolver, settingName, overrideValue)
+ fun restore() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
}
+ }
- override fun close() {
- Settings.Global.putInt(contentResolver, settingName, initialValue)
+ private class FlagsParameterizationProvider : TestParameterValuesProvider() {
+ override fun provideValues(
+ context: TestParameterValuesProvider.Context
+ ): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH,
+ DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
+ )
}
}
companion object {
const val EXTERNAL_DISPLAY_ID = 100
- enum class ModeSwitchTestCase(
+ enum class SwitchState(val value: Int) {
+ UNKNOWN(InputManager.SWITCH_STATE_UNKNOWN),
+ ON(InputManager.SWITCH_STATE_ON),
+ OFF(InputManager.SWITCH_STATE_OFF),
+ }
+
+ enum class ExternalDisplayBasedTargetModeTestCase(
val defaultWindowingMode: Int,
+ val hasExternalDisplay: Boolean,
val extendedDisplayEnabled: Boolean,
- val expectToSwitchByDefault: Boolean,
+ val expectedWindowingMode: Int,
) {
- FULLSCREEN_DISPLAY(
+ FREEFORM_EXTERNAL_EXTENDED(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FULLSCREEN_EXTERNAL_EXTENDED(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = true,
extendedDisplayEnabled = true,
- expectToSwitchByDefault = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FULLSCREEN_DISPLAY_MIRRORING(
+ FREEFORM_NO_EXTERNAL_EXTENDED(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FULLSCREEN_NO_EXTERNAL_EXTENDED(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ FREEFORM_EXTERNAL_MIRROR(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = true,
extendedDisplayEnabled = false,
- expectToSwitchByDefault = false,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FREEFORM_DISPLAY(
+ FULLSCREEN_EXTERNAL_MIRROR(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ FREEFORM_NO_EXTERNAL_MIRROR(
defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FULLSCREEN_NO_EXTERNAL_MIRROR(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ }
+
+ enum class FormFactorBasedTargetModeTestCase(
+ val hasExternalDisplay: Boolean,
+ val extendedDisplayEnabled: Boolean,
+ val tabletModeStatus: SwitchState,
+ val expectedWindowingMode: Int,
+ ) {
+ EXTERNAL_EXTENDED_TABLET(
+ hasExternalDisplay = true,
extendedDisplayEnabled = true,
- expectToSwitchByDefault = false,
+ tabletModeStatus = SwitchState.ON,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FREEFORM_DISPLAY_MIRRORING(
- defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ NO_EXTERNAL_EXTENDED_TABLET(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.ON,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_MIRROR_TABLET(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.ON,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_MIRROR_TABLET(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.ON,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_EXTENDED_CLAMSHELL(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.OFF,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ NO_EXTERNAL_EXTENDED_CLAMSHELL(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.OFF,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ EXTERNAL_MIRROR_CLAMSHELL(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.OFF,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ NO_EXTERNAL_MIRROR_CLAMSHELL(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.OFF,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ EXTERNAL_EXTENDED_UNKNOWN(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ NO_EXTERNAL_EXTENDED_UNKNOWN(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_MIRROR_UNKNOWN(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_MIRROR_UNKNOWN(
+ hasExternalDisplay = false,
extendedDisplayEnabled = false,
- expectToSwitchByDefault = false,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index 006c3cae121c..4c18ee1500b7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -114,6 +114,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
transactionSupplier = transactionSupplier,
)
desktopRepository = userRepositories.current
+ desktopRepository.addDesk(DEFAULT_DISPLAY, DEFAULT_DESK_ID)
+ desktopRepository.setActiveDesk(DEFAULT_DISPLAY, DEFAULT_DESK_ID)
}
@Test
@@ -835,5 +837,6 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
companion object {
private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900)
private val DISPLAY_BOUNDS = Rect(0, 0, 2000, 2000)
+ private const val DEFAULT_DESK_ID = 0
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index e9f92cfd7c56..0c585b3e843a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -431,6 +431,38 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX,
+ )
+ fun startAndAnimateLaunchTransition_withMinimizeChange_wrongTaskId_reparentsMinimizeChange() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val launchTaskChange = createChange(launchingTask, mode = TRANSIT_OPEN)
+ val minimizeChange = createChange(minimizingTask)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = Int.MAX_VALUE,
+ minimizingTaskId = minimizingTask.taskId,
+ )
+ mixedHandler.startAnimation(
+ transition,
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) {}
+
+ verify(rootTaskDisplayAreaOrganizer)
+ .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any())
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
val wct = WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 8a5acfa70f50..695cf600b359 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -23,7 +23,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
-import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
@@ -102,7 +102,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -127,7 +127,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -135,7 +135,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -157,7 +157,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
assertThat(desktopModeEventLogger.currentSessionId.get()).isEqualTo(NO_SESSION_ID)
}
@@ -166,7 +166,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -205,7 +205,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -213,7 +213,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -252,7 +252,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -260,7 +260,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -302,7 +302,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -346,7 +346,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -390,7 +390,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_FOCUS_REASON),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -434,7 +434,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(FocusReason.UNKNOWN.reason),
)
}
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -446,7 +446,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
@@ -485,7 +485,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
)
verifyNoLogging()
- verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index b7d25b5255f8..bd37610ae65b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -83,7 +83,7 @@ import org.mockito.kotlin.same
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/**
@@ -596,7 +596,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
.logTaskRemoved(
eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON))
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -668,7 +668,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
.logTaskInfoChanged(
eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -701,7 +701,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
)
)
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -729,7 +729,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
)
)
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -753,7 +753,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
.logTaskInfoChanged(
eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
// task 2 resize
val newTaskInfo2 =
@@ -781,7 +781,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
)
)
)
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
@Test
@@ -892,14 +892,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
eq(taskUpdate.visibleTaskCount.toString()),
)
}
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
private fun verifyTaskRemovedAndExitLogging(exitReason: ExitReason, taskUpdate: TaskUpdate) {
assertFalse(transitionObserver.isSessionActive)
verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate))
verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason))
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyNoMoreInteractions(desktopModeEventLogger)
}
private companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt
new file mode 100644
index 000000000000..6a99d4770728
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.util.StubTransaction
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() {
+ private lateinit var handler: DesktopModeMoveToDisplayTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler = DesktopModeMoveToDisplayTransitionHandler(StubTransaction())
+ }
+
+ @Test
+ fun handleRequest_returnsNull() {
+ assertNull(handler.handleRequest(mock(), mock()))
+ }
+
+ @Test
+ fun startAnimation_changeWithinDisplay_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ setDisplayId(/* start= */ 1, /* end= */ 1)
+ }
+ )
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = mock(),
+ )
+
+ assertFalse("Should not animate open transition", animates)
+ }
+
+ @Test
+ fun startAnimation_changeMoveToDisplay_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ setDisplayId(/* start= */ 1, /* end= */ 2)
+ }
+ )
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = mock(),
+ )
+
+ assertTrue("Should animate display change transition", animates)
+ }
+
+ @Test
+ fun startAnimation_movingActivityEmbedding_shouldSetCorrectBounds() {
+ val leashLeft = mock<SurfaceControl>()
+ val leashRight = mock<SurfaceControl>()
+ val leashContainer = mock<SurfaceControl>()
+ val startTransaction = spy(StubTransaction())
+
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ flags = TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
+ leash = leashLeft
+ setDisplayId(/* start= */ 1, /* end= */ 2)
+ setEndAbsBounds(
+ Rect(
+ /* left= */ 100,
+ /* top= */ 100,
+ /* right= */ 500,
+ /* bottom= */ 700,
+ )
+ )
+ setEndRelOffset(/* left= */ 0, /* top= */ 0)
+ }
+ )
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ flags = TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
+ leash = leashRight
+ setDisplayId(1, 2)
+ setEndAbsBounds(
+ Rect(
+ /* left= */ 500,
+ /* top= */ 100,
+ /* right= */ 900,
+ /* bottom= */ 700,
+ )
+ )
+ setEndRelOffset(/* left= */ 400, /* top= */ 0)
+ }
+ )
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ flags = TransitionInfo.FLAG_TRANSLUCENT
+ leash = leashContainer
+ setDisplayId(/* start= */ 1, /* end= */ 2)
+ setEndAbsBounds(
+ Rect(
+ /* left= */ 100,
+ /* top= */ 100,
+ /* right= */ 900,
+ /* bottom= */ 700,
+ )
+ )
+ setEndRelOffset(/* left= */ 100, /* top= */ 100)
+ }
+ )
+ },
+ startTransaction = startTransaction,
+ finishTransaction = StubTransaction(),
+ finishCallback = mock(),
+ )
+
+ verify(startTransaction).setPosition(leashLeft, 0f, 0f)
+ verify(startTransaction).setPosition(leashRight, 400f, 0f)
+ verify(startTransaction).setPosition(leashContainer, 100f, 100f)
+ verify(startTransaction).setWindowCrop(leashLeft, 400, 600)
+ verify(startTransaction).setWindowCrop(leashRight, 400, 600)
+ verify(startTransaction).setWindowCrop(leashContainer, 800, 600)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index dcc9e2415039..b859a00c6df4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -20,14 +20,15 @@ import android.animation.AnimatorTestRule
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.PointF
import android.graphics.Rect
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
-import android.view.Display
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
@@ -45,6 +46,8 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
/**
@@ -63,7 +66,6 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
private lateinit var taskInfo: RunningTaskInfo
@Mock private lateinit var syncQueue: SyncTransactionQueue
@Mock private lateinit var displayController: DisplayController
- @Mock private lateinit var display: Display
@Mock private lateinit var taskSurface: SurfaceControl
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var displayLayout: DisplayLayout
@@ -79,12 +81,20 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
- whenever(displayController.getDisplay(anyInt())).thenReturn(display)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display)
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(any()))
.thenReturn(Rect())
taskInfo = DesktopTestHelpers.createFullscreenTask()
+
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_isDesktopModeSupported,
+ true,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_canInternalDisplayHostDesktops,
+ true,
+ )
}
@Test
@@ -256,14 +266,9 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
)
fun testDefaultIndicatorWithNoDesktop() {
mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_isDesktopModeSupported,
- false,
- )
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_isDesktopModeDevOptionSupported,
+ com.android.internal.R.bool.config_canInternalDisplayHostDesktops,
false,
)
-
// Fullscreen to center, no desktop indicator
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
var result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
@@ -345,6 +350,38 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
assertThat(visualIndicator.indicatorBounds).isEqualTo(dropTargetBounds)
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_inTransitionFlagDisabled_isAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromFreeform_inTransitionFlagEnabled_isAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+
+ verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromFullscreen_inTransitionFlagEnabled_notAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromSplit_inTransitionFlagEnabled_notAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+
+ verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any())
+ }
+
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
visualIndicator =
DesktopModeVisualIndicator(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
new file mode 100644
index 000000000000..ef394d81cc57
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.TRANSIT_PIP
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesktopPipTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopPipTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopPipTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var observer: DesktopPipTransitionObserver
+
+ private val transition = Binder()
+ private var onSuccessInvokedCount = 0
+
+ @Before
+ fun setUp() {
+ observer = DesktopPipTransitionObserver()
+
+ onSuccessInvokedCount = 0
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onTransitionReady_taskInPinnedWindowingMode_onSuccessInvoked() {
+ val taskId = 1
+ val pipTransition = createPendingPipTransition(taskId)
+ val successfulChange = createChange(taskId, WINDOWING_MODE_PINNED)
+ observer.addPendingPipTransition(pipTransition)
+
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(
+ TRANSIT_PIP, /* flags= */
+ 0
+ ).apply { addChange(successfulChange) },
+ )
+
+ assertThat(onSuccessInvokedCount).isEqualTo(1)
+ }
+
+ private fun createPendingPipTransition(
+ taskId: Int
+ ): DesktopPipTransitionObserver.PendingPipTransition {
+ return DesktopPipTransitionObserver.PendingPipTransition(
+ token = transition,
+ taskId = taskId,
+ onSuccess = { onSuccessInvokedCount += 1 },
+ )
+ }
+
+ private fun createChange(taskId: Int, windowingMode: Int): TransitionInfo.Change {
+ return TransitionInfo.Change(mock(), mock()).apply {
+ taskInfo =
+ TestRunningTaskInfoBuilder()
+ .setTaskId(taskId)
+ .setWindowingMode(windowingMode)
+ .build()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index a43b4dd00911..a10aeca95bce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -26,7 +26,6 @@ import android.view.Display.INVALID_DISPLAY
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
@@ -89,7 +88,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo = DesktopRepository(persistentRepository, datastoreScope, DEFAULT_USER_ID)
+ repo = spy(DesktopRepository(persistentRepository, datastoreScope, DEFAULT_USER_ID))
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
.thenReturn(Desktop.getDefaultInstance())
shellInit.init()
@@ -276,6 +275,18 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeActiveTask_excludingDesk_leavesTaskInDesk() {
+ repo.addDesk(displayId = 2, deskId = 11)
+ repo.addDesk(displayId = 3, deskId = 12)
+ repo.addTaskToDesk(displayId = 3, deskId = 12, taskId = 100, isVisible = true)
+
+ repo.removeActiveTask(taskId = 100, excludedDeskId = 12)
+
+ assertThat(repo.getActiveTaskIdsInDesk(12)).contains(100)
+ }
+
+ @Test
fun isActiveTask_nonExistingTask_returnsFalse() {
assertThat(repo.isActiveTask(99)).isFalse()
}
@@ -1172,6 +1183,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
val tasksBeforeRemoval = repo.removeDesk(deskId = DEFAULT_DISPLAY)
+ verify(repo, times(1)).notifyVisibleTaskListeners(DEFAULT_DISPLAY, visibleTasksCount = 0)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
}
@@ -1185,6 +1197,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.removeDesk(deskId = 3)
+ verify(repo, times(1)).notifyVisibleTaskListeners(DEFAULT_DISPLAY, visibleTasksCount = 0)
assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(3)
}
@@ -1197,10 +1210,22 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.removeDesk(deskId = 2)
+ verify(repo, times(1)).notifyVisibleTaskListeners(DEFAULT_DISPLAY, visibleTasksCount = 0)
assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2)
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun removeDesk_removesFromPersistence() =
+ runTest(StandardTestDispatcher()) {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+
+ repo.removeDesk(deskId = 2)
+
+ verify(persistentRepository).removeDesktop(DEFAULT_USER_ID, 2)
+ }
+
+ @Test
fun getTaskInFullImmersiveState_byDisplay() {
repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1212,69 +1237,6 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
- fun setTaskInPip_savedAsMinimizedPipInDisplay() {
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- }
-
- @Test
- fun removeTaskInPip_removedAsMinimizedPipInDisplay() {
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
- }
-
- @Test
- fun setTaskInPip_multipleDisplays_bothAreInPip() {
- repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun setPipShouldKeepDesktopActive_shouldKeepDesktopActive() {
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = true)
-
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun setPipShouldNotKeepDesktopActive_shouldNotKeepDesktopActive() {
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
-
- repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = false)
-
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun removeTaskInPip_shouldNotKeepDesktopActive() {
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
-
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun addTask_deskDoesNotExists_createsDesk() {
repo.addTask(displayId = 999, taskId = 6, isVisible = true)
@@ -1458,6 +1420,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.removeDesk(deskId = 1)
executor.flushAll()
+ verify(repo, times(1)).notifyVisibleTaskListeners(DEFAULT_DISPLAY, visibleTasksCount = 0)
val lastRemoval = assertNotNull(listener.lastRemoval)
assertThat(lastRemoval.displayId).isEqualTo(0)
assertThat(lastRemoval.deskId).isEqualTo(1)
@@ -1474,6 +1437,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.removeDesk(deskId = 2)
executor.flushAll()
+ verify(repo, times(0)).notifyVisibleTaskListeners(DEFAULT_DISPLAY, visibleTasksCount = 0)
assertThat(listener.lastRemoval).isNull()
}
@@ -1489,6 +1453,7 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.removeDesk(deskId = 1)
executor.flushAll()
+ verify(repo, times(1)).notifyVisibleTaskListeners(DEFAULT_DISPLAY, visibleTasksCount = 0)
val lastActivationChange = assertNotNull(listener.lastActivationChange)
assertThat(lastActivationChange.displayId).isEqualTo(0)
assertThat(lastActivationChange.oldActive).isEqualTo(1)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
index 6b0ee5b7ffd4..4ace1b2d7c71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
@@ -119,9 +119,8 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskChanging_freeformTask_nonActiveTaskInDesktopRepo_addsTaskToDesktopRepo() {
+ fun onTaskChanging_freeformTask_addsTaskToDesktopRepo() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
desktopTaskChangeListener.onTaskChanging(task)
@@ -129,28 +128,6 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskChanging_freeformTask_activeVisibleTaskInDesktopRepo_updatesTaskVisibility() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
-
- desktopTaskChangeListener.onTaskChanging(task)
-
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, task.isVisible)
- }
-
- @Test
- fun onTaskChanging_freeformTask_activeNonVisibleTask_updatesTaskVisibility() {
- val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
-
- desktopTaskChangeListener.onTaskChanging(task)
-
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, task.isVisible)
- }
-
- @Test
fun onTaskMovingToFront_fullscreenTask_activeTaskInDesktopRepo_removesTaskFromRepo() {
val task = createFullscreenTask().apply { isVisible = true }
whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
@@ -180,6 +157,28 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
+ fun onTaskMovingToBack_activeTaskInRepo_updatesTask() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskMovingToBack(task)
+
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, /* isVisible= */ false)
+ }
+
+ @Test
+ fun onTaskMovingToBack_nonActiveTaskInRepo_noop() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskMovingToBack(task)
+
+ verify(desktopUserRepositories.current, never())
+ .updateTask(task.displayId, task.taskId, /* isVisible= */ false)
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() {
val task = createFreeformTask().apply { isVisible = true }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 63bf6841dba4..7efcd4fc3c8f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -22,6 +22,7 @@ import android.app.ActivityOptions
import android.app.KeyguardManager
import android.app.PendingIntent
import android.app.PictureInPictureParams
+import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -31,6 +32,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.CONFIG_DENSITY
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
@@ -55,6 +57,7 @@ import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableContext
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
import android.view.DragEvent
import android.view.Gravity
import android.view.MotionEvent
@@ -85,9 +88,9 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
@@ -102,6 +105,7 @@ import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.HomeIntentProvider
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -261,9 +265,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
+ @Mock private lateinit var desktopPipTransitionObserver: DesktopPipTransitionObserver
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mockDisplayContext: Context
@Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
+ @Mock
+ private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -274,6 +281,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private lateinit var testScope: CoroutineScope
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private lateinit var spyContext: TestableContext
+ private lateinit var homeIntentProvider: HomeIntentProvider
private val shellExecutor = TestShellExecutor()
private val bgExecutor = TestShellExecutor()
@@ -293,6 +301,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private val wallpaperToken = MockToken().token()
private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setUp() {
Dispatchers.setMain(StandardTestDispatcher())
@@ -322,12 +334,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
transitions,
userRepositories,
shellTaskOrganizer,
+ desksOrganizer,
MAX_TASK_LIMIT,
mockInteractionJankMonitor,
mContext,
mockHandler,
)
desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext))
+ homeIntentProvider = HomeIntentProvider(context)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), anyOrNull())).thenAnswer { Binder() }
@@ -380,6 +394,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(userProfileContexts[anyInt()]).thenReturn(context)
whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
+ whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -427,6 +442,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
dragToDesktopTransitionHandler,
mMockDesktopImmersiveController,
userRepositories,
+ repositoryInitializer,
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
@@ -443,9 +459,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionsObserver,
+ Optional.of(desktopPipTransitionObserver),
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler,
+ homeIntentProvider,
)
@After
@@ -619,11 +638,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isDesktopModeShowing_noTasks_returnsFalse() {
assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -634,6 +655,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isDesktopModeShowing_noActiveDesk_returnsFalse() {
+ taskRepository.setDeskInactive(deskId = 0)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -648,6 +678,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isDesktopModeShowing_topTransparentFullscreenTask_returnsTrue() {
val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
@@ -656,38 +687,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun isDesktopModeShowing_minimizedPipTask_wallpaperVisible_returnsTrue() {
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible())
- .thenReturn(true)
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun isDesktopModeShowing_deskInactive_topTransparentFullscreenTask_returnsTrue() {
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
+ taskRepository.setDeskInactive(deskId = 0)
assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue()
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun isDesktopModeShowing_minimizedPipTask_wallpaperNotVisible_returnsFalse() {
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible())
- .thenReturn(false)
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
-
- assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun isDesktopModeShowing_pipTaskNotMinimizedNorVisible_returnsFalse() {
- setUpPipTask(autoEnterEnabled = true)
-
- assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
@@ -1077,11 +1090,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isAnyDeskActive_noTasks_returnsFalse() {
assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_noActiveDesk_returnsFalse() {
+ taskRepository.setDeskInactive(deskId = 0)
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_withActiveDesk_returnsTrue() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isAnyDeskActive_twoTasks_bothVisible_returnsTrue() {
setUpHomeTask()
@@ -1092,6 +1123,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isInDesktop_twoTasks_oneVisible_returnsTrue() {
setUpHomeTask()
@@ -1102,6 +1134,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isAnyDeskActive_twoTasksVisibleOnDifferentDisplays_returnsTrue() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1121,7 +1154,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
}
@Test
@@ -1132,7 +1165,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
}
@Test
@@ -1143,7 +1176,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
}
@Test
@@ -1154,7 +1187,55 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES)
+ fun addMoveToDeskTaskChanges_newTaskInstance_inheritsClosingInstanceBounds() {
+ // Setup existing task.
+ val existingTask = setUpFreeformTask(active = true)
+ val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class")
+ existingTask.topActivity = testComponent
+ existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
+ // Set up new instance of already existing task.
+ val launchingTask = setUpFullscreenTask()
+ launchingTask.topActivity = testComponent
+ launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+
+ // Move new instance to desktop. By default multi instance is not supported so first
+ // instance will close.
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, launchingTask, deskId = 0)
+
+ // New instance should inherit task bounds of old instance.
+ assertThat(findBoundsChange(wct, launchingTask))
+ .isEqualTo(existingTask.configuration.windowConfiguration.bounds)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES)
+ fun handleRequest_newTaskInstance_inheritsClosingInstanceBounds() {
+ setUpLandscapeDisplay()
+ // Setup existing task.
+ val existingTask = setUpFreeformTask(active = true)
+ val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class")
+ existingTask.topActivity = testComponent
+ existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
+ // Set up new instance of already existing task.
+ val launchingTask = setUpFreeformTask(active = false)
+ taskRepository.removeTask(launchingTask.displayId, launchingTask.taskId)
+ launchingTask.topActivity = testComponent
+ launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+
+ // Move new instance to desktop. By default multi instance is not supported so first
+ // instance will close.
+ val wct = controller.handleRequest(Binder(), createTransition(launchingTask))
+
+ assertNotNull(wct, "should handle request")
+ val finalBounds = findBoundsChange(wct, launchingTask)
+ // New instance should inherit task bounds of old instance.
+ assertThat(finalBounds).isEqualTo(existingTask.configuration.windowConfiguration.bounds)
}
@Test
@@ -1388,7 +1469,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- val captionInsets = getAppHeaderHeight(context)
+ val captionInsets = getDesktopViewAppHeaderHeightPx(context)
finalBounds!!.top += captionInsets
val finalAspectRatio =
maxOf(finalBounds.height(), finalBounds.width()) /
@@ -1410,7 +1491,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- val captionInsets = getAppHeaderHeight(context)
+ val captionInsets = getDesktopViewAppHeaderHeightPx(context)
finalBounds!!.top += captionInsets
val finalAspectRatio =
maxOf(finalBounds.height(), finalBounds.width()) /
@@ -1838,6 +1919,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wallpaperToken = MockToken().token()
whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
.thenReturn(wallpaperToken)
+ taskRepository.addDesk(SECOND_DISPLAY, deskId = 2)
val task = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2, background = true)
controller.moveTaskToDefaultDeskAndActivate(
@@ -2393,6 +2475,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveTaskToFront_postsWctWithReorderOp() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2415,9 +2498,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_reordersToFront() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(task1.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task1, remoteTransition = null)
+
+ verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(task1))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_bringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() {
setUpHomeTask()
- val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ }
whenever(
desktopMixedTransitionHandler.startLaunchTransition(
eq(TRANSIT_TO_FRONT),
@@ -2438,6 +2546,32 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_bringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() {
+ val deskId = 0
+ setUpHomeTask()
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTasks[0].taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[1])
+ }
+
+ @Test
fun moveTaskToFront_minimizedTask_marksTaskAsUnminimized() {
val transition = Binder()
val freeformTask = setUpFreeformTask()
@@ -2526,8 +2660,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
val task = createRecentTaskInfo(1001)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
whenever(
@@ -2550,15 +2689,43 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val task = createRecentTaskInfo(1001)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
fun moveToNextDisplay_noOtherDisplays() {
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
controller.moveToNextDisplay(task.taskId)
- verifyWCTNotExecuted()
+ verify(transitions, never()).startTransition(anyInt(), any(), anyOrNull())
}
@Test
- fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksDisabled() {
// Set up two display ids
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
@@ -2572,16 +2739,39 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
val taskChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find {
- it.container == task.token.asBinder() && it.isReparent
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { it.container == task.token.asBinder() && it.isReparent }
assertNotNull(taskChange)
assertThat(taskChange.newParent).isEqualTo(secondDisplayArea.token.asBinder())
assertThat(taskChange.toTop).isTrue()
}
@Test
- fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksEnabled() {
+ // Set up two display ids
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksDisabled() {
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -2595,15 +2785,37 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
val taskChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find {
- it.container == task.token.asBinder() && it.isReparent
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { it.container == task.token.asBinder() && it.isReparent }
assertNotNull(taskChange)
assertThat(taskChange.newParent).isEqualTo(defaultDisplayArea.token.asBinder())
assertThat(taskChange.toTop).isTrue()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksEnabled() {
+ // Set up two display ids
+ val targetDeskId = 0
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task))
+ }
+
+ @Test
@EnableFlags(
FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
@@ -2622,7 +2834,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(type = TRANSIT_CHANGE)) {
+ with(
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ ) {
val wallpaperChange =
hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }
assertNotNull(wallpaperChange)
@@ -2648,9 +2865,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
val wallpaperChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find { op ->
- op.container == wallpaperToken.asBinder()
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { op -> op.container == wallpaperToken.asBinder() }
assertNotNull(wallpaperChange)
assertThat(wallpaperChange.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
}
@@ -2659,6 +2879,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
fun moveToNextDisplay_sizeInDpPreserved() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -2682,7 +2903,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
// To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin
// to the right margin and the ratio of the top margin to bottom margin are also
@@ -2695,6 +2921,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
fun moveToNextDisplay_shiftWithinDestinationDisplayBounds() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -2719,7 +2946,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
assertThat(taskChange.configuration.windowConfiguration.bounds)
.isEqualTo(Rect(960, 480, 1280, 720))
@@ -2731,6 +2963,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
// Create a mock for the target display area: second display
val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
@@ -2750,7 +2983,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
// DP size is preserved. The window is centered in the destination display.
assertThat(taskChange.configuration.windowConfiguration.bounds)
@@ -2788,7 +3026,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
assertThat(taskChange.configuration.windowConfiguration.bounds.left).isAtLeast(0)
assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0)
@@ -2815,16 +3058,22 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.moveToNextDisplay(task.taskId)
val taskChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find {
- it.container == task.token.asBinder() && it.type == HIERARCHY_OP_TYPE_REORDER
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find {
+ it.container == task.token.asBinder() && it.type == HIERARCHY_OP_TYPE_REORDER
+ }
assertNotNull(taskChange)
assertThat(taskChange.toTop).isTrue()
assertThat(taskChange.includingParents()).isTrue()
}
@Test
- fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksDisabled_bringsExistingTasksToFront() {
val transition = Binder()
val sourceDeskId = 0
val targetDeskId = 2
@@ -2850,6 +3099,64 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksEnabled_bringsExistingTasksToFront() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+ val task2 = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+
+ controller.moveToNextDisplay(task1.taskId)
+
+ // Existing desktop task in the target display is moved to front.
+ val wct = getLatestTransition()
+ assertNotNull(wct)
+ verify(desksOrganizer).reorderTaskToFront(wct, targetDeskId, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimit() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ val targetDeskTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ }
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val wct = getLatestTransition()
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, targetDeskId, targetDeskTasks[0])
+ }
+
+ @Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
@@ -2964,7 +3271,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
fun moveToNextDisplay_movingToDesktop_sendsTaskbarRoundingUpdate() {
val transition = Binder()
- val sourceDeskId = 1
+ val sourceDeskId = 0
val targetDeskId = 2
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
taskRepository.setDeskInactive(deskId = targetDeskId)
@@ -3103,42 +3410,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun onDesktopWindowClose_minimizedPipPresent_doesNotExitDesktop() {
- val freeformTask = setUpFreeformTask().apply { isFocused = true }
- val pipTask = setUpPipTask(autoEnterEnabled = true)
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
-
- verifyExitDesktopWCTNotExecuted()
- }
-
- @Test
- @EnableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
- )
- fun onDesktopWindowClose_minimizedPipNotPresent_exitDesktop() {
- val freeformTask = setUpFreeformTask()
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
-
- controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
- verifyExitDesktopWCTNotExecuted()
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false)
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
-
- // Moves wallpaper activity to back when leaving desktop
- wct.assertReorder(wallpaperToken, toTop = false)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun onDesktopWindowClose_lastWindow_deactivatesDesk() {
val task = setUpFreeformTask()
@@ -3234,6 +3505,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
val task = setUpPipTask(autoEnterEnabled = true)
val handler = mock(TransitionHandler::class.java)
@@ -3248,6 +3520,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
val task = setUpPipTask(autoEnterEnabled = false)
whenever(
@@ -3267,23 +3540,87 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun onPipTaskMinimize_doesntRemoveWallpaper() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
+ fun onDesktopTaskEnteredPip_pipIsLastTask_removesWallpaper() {
val task = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
- controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
- val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
- assertThat(
- captor.firstValue.hierarchyOps.none { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
- hop.container == wallpaperToken.asBinder()
- }
- )
- .isTrue()
+ // Wallpaper is moved to the back
+ val wct = getLatestTransition()
+ wct.assertReorder(wallpaperToken, /* toTop= */ false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDesktopTaskEnteredPip_pipIsLastTask_deactivatesDesk() {
+ val deskId = DEFAULT_DISPLAY
+ val task = setUpPipTask(autoEnterEnabled = true, deskId = deskId)
+ val transition = Binder()
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = deskId,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ verify(desksOrganizer).deactivateDesk(any(), eq(deskId))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopTaskEnteredPip_pipIsLastTask_launchesHome() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ val wct = getLatestTransition()
+ wct.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopTaskEnteredPip_pipIsNotLastTask_doesntExitDesktopMode() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ val deskId = DEFAULT_DISPLAY
+ setUpFreeformTask(deskId = deskId) // launch another freeform task
+ val transition = Binder()
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = deskId,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = false,
+ )
+
+ // No transition to exit Desktop mode is started
+ verifyWCTNotExecuted()
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ verify(desksOrganizer, never()).deactivateDesk(any(), eq(deskId))
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
}
@Test
@@ -3537,7 +3874,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -3553,7 +3891,22 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_deskActive_multiDesksEnabled_movesToDesk() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = deskId)
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -3578,7 +3931,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_activeDesk_multiDesksEnabled_movesToDesk() {
+ val deskId = 0
+ setUpHomeTask()
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksDisabled_dontMinimize() {
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val fullscreenTask = createFullscreenTask()
@@ -3591,7 +3960,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksEnabled_dontMinimize() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also {
+ markTaskVisible(it)
+ }
+
+ // Launch a fullscreen task while in the desk.
+ val fullscreenTask = createFullscreenTask()
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(deskId), any())
+ assertNull(desktopTasksLimiter.getMinimizingTask(transition))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
@@ -3605,7 +3996,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also {
+ markTaskVisible(it)
+ }
+ }
+
+ // Launch a fullscreen task while in the desk.
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ val minimizingTask =
+ assertNotNull(desktopTasksLimiter.getMinimizingTask(transition)?.taskId)
+ assertThat(minimizingTask).isEqualTo(freeformTasks[0].taskId)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
@@ -3614,13 +4032,43 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
// Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(8)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(8, freeformTasks[0], toTop = false)
+ // Oldest task that needs to minimized is never reordered to top over Home.
+ val taskToMinimize = freeformTasks[0]
+ wct.assertWithoutHop { hop ->
+ hop.container == taskToMinimize.token &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop == true
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ // The launching task is moved to the desk.
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ // The bottom-most task is minimized.
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
}
@Test
- fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksDisabled_existingAndNewTasksAreMinimized() {
val minimizedTask = setUpFreeformTask()
taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
@@ -3631,16 +4079,105 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
// Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
// task is under the home task.
wct.assertReorderAt(1, homeTask, toTop = true)
- wct.assertReorderAt(9, freeformTasks[0], toTop = false)
+ // Oldest task that needs to minimized is never reordered to top over Home.
+ val taskToMinimize = freeformTasks[0]
+ wct.assertWithoutHop { hop ->
+ hop.container == taskToMinimize.token &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop == true
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksEnabled_existingAndNewTasksAreMinimized() {
+ // A desk with a minimized tasks, and non-minimized tasks already at the task limit.
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val minimizedTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.minimizeTaskInDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = deskId,
+ taskId = minimizedTask.taskId,
+ )
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also {
+ markTaskVisible(it)
+ }
+ }
+
+ // Launch a fullscreen task that brings Home to front with it.
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_taskAddedToDesk() {
+ // A desk with a minimized tasks, and non-minimized tasks already at the task limit.
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ // Launch a fullscreen task that brings Home to front with it.
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun handleRequest_fullscreenTaskWithTaskOnHome_activatesDesk() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ // Launch a fullscreen task that brings Home to front with it.
+ val homeTask = setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ wct.assertReorder(homeTask, toTop = true)
+ wct.assertReorder(wallpaperToken, toTop = true)
+ verify(desksOrganizer).activateDesk(wct, deskId)
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ DeskTransition.ActiveDeskWithTask(
+ token = transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = deskId,
+ enterTaskId = fullscreenTask.taskId,
+ )
+ )
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
@@ -3663,7 +4200,28 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_freeformDisplay_movesToDesk() {
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_notInDesk_enforceDesktop_fullscreenDisplay_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -3675,6 +4233,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
val freeformTask = setUpFreeformTask()
markTaskHidden(freeformTask)
@@ -3683,12 +4242,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
val fullscreenTask = createFullscreenTask()
assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_notInDesk_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val fullscreenTask = createFullscreenTask()
+
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -3699,8 +4269,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_deskInOtherDisplayActive_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
+
+ assertThat(result).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksDisabled_minimize() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
freeformTasks.forEach { markTaskVisible(it) }
val newFreeformTask = createFreeformTask()
@@ -3713,6 +4302,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksEnabled_minimize() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val newFreeformTask = createFreeformTask()
+
+ val wct =
+ controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() {
val deskId = 0
val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
@@ -3727,7 +4334,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
- val freeformTask = setUpFreeformTask()
+ taskRepository.setDeskInactive(deskId = 0)
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
markTaskHidden(freeformTask)
val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
@@ -3757,9 +4365,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
-
- val freeformTask = setUpFreeformTask()
+ taskRepository.setDeskInactive(deskId = 0)
+ val freeformTask = setUpFreeformTask(DEFAULT_DISPLAY, deskId = 0)
markTaskHidden(freeformTask)
+
val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
assertNotNull(wct, "should handle request")
@@ -3768,7 +4377,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -3787,7 +4399,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_multiDesksDisabled_reorderedToTop() {
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -3810,34 +4423,47 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
- val task = createFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task))
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_notInDesk_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+ val freeformTask1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTask2 = createFreeformTask()
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(1)
- result.assertReorderAt(0, task, toTop = true)
+ val wct =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT),
+ )
+
+ assertNotNull(wct, "Should handle request")
+ verify(desksOrganizer).reorderTaskToFront(wct, deskId, freeformTask1)
+ wct.assertReorder(freeformTask2, toTop = true)
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
val task = createFreeformTask()
-
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(2)
- // Add desktop wallpaper activity
- result.assertPendingIntentAt(0, desktopWallpaperIntent)
- // Bring new task to front
- result.assertReorderAt(1, task, toTop = true)
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, task, toTop = true)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
@@ -3853,10 +4479,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ taskRepository.setDeskInactive(deskId = 0)
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
- createFreeformTask(displayId = SECOND_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2)
val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
@@ -3992,7 +4621,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -4009,6 +4639,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksEnabled_returnSwitchToFreeformWCT() {
+ val deskId = 0
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ markTaskVisible(freeformTask)
+
+ val task =
+ setUpFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = true
+ numActivities = 1
+ }
+
+ val wct = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, task)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
@DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
@@ -4067,7 +4720,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
)
- fun handleRequest_onlyTopTransparentFullscreenTask_returnSwitchToFreeformWCT() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksDisabled_returnSwitchToFreeformWCT() {
val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
@@ -4079,8 +4733,28 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksEnabled_movesToDesktop() {
+ val deskId = 0
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
+ taskRepository.setDeskInactive(deskId = deskId)
+
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ val wct = controller.handleRequest(Binder(), createTransition(task))
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, task)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
@@ -4134,7 +4808,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_systemUIActivityWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -4153,6 +4828,32 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_systemUIActivityWithoutDisplay_multiDesksEnabled_movesTaskToDesk() {
+ val deskId = 0
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ setUpFullscreenTask(displayId = DEFAULT_DISPLAY).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ val wct = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, task)
+ }
+
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() {
val freeformTask = setUpFreeformTask()
@@ -4195,6 +4896,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() {
+ taskRepository.setDeskInactive(deskId = 0)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -4402,6 +5104,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -4735,32 +5438,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
- )
- fun moveFocusedTaskToFullscreen_minimizedPipPresent_removeWallpaperActivity() {
- val freeformTask = setUpFreeformTask()
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
-
- controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
- verifyExitDesktopWCTNotExecuted()
-
- freeformTask.isFocused = true
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[freeformTask.token.asBinder()])
- assertThat(taskChange.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- // Moves wallpaper activity to back when leaving desktop
- wct.assertReorder(wallpaperToken, toTop = false)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun removeDesk_multipleTasks_removesAll() {
@@ -4854,6 +5531,55 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
)
+ fun activateDesk_hasNonRunningTask_startsTask() {
+ val deskId = 0
+ val nonRunningTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0, background = true)
+
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ val wct = getLatestWct(TRANSIT_TO_FRONT, OneShotRemoteHandler::class.java)
+ assertNotNull(wct)
+ wct.assertLaunchTask(nonRunningTask.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun activateDesk_hasRunningTask_reordersTask() {
+ val deskId = 0
+ val runningTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ verify(desksOrganizer).reorderTaskToFront(any(), eq(deskId), eq(runningTask))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun moveTaskToDesk_multipleDesks_addsPendingTransition() {
val transition = Binder()
whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
@@ -5368,7 +6094,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
// Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
// Assert that task leash is updated via Surface Animations
verify(mReturnToDragStartAnimator)
.start(
@@ -5639,7 +6366,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromFreeformAddsNewWindow() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun openInstance_fromFreeformAddsNewWindow_multiDesksDisabled() {
setUpLandscapeDisplay()
val task = setUpFreeformTask()
val taskToRequest = setUpFreeformTask()
@@ -5653,7 +6381,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
)
.thenReturn(Binder())
+
runOpenInstance(task, taskToRequest.taskId)
+
verify(desktopMixedTransitionHandler)
.startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull())
val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
@@ -5662,10 +6392,42 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun openInstance_fromFreeformAddsNewWindow_multiDesksEnabled() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val taskToRequest = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(taskToRequest.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ runOpenInstance(task, taskToRequest.taskId)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull())
+ verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(taskToRequest))
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromFreeform_minimizesIfNeeded() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun openInstance_fromFreeform_multiDesksDisabled_minimizesIfNeeded() {
setUpLandscapeDisplay()
- val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
val oldestTask = freeformTasks.first()
val newestTask = freeformTasks.last()
@@ -5691,6 +6453,40 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun openInstance_fromFreeform_multiDesksEnabled_minimizesIfNeeded() {
+ setUpLandscapeDisplay()
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val oldestTask = freeformTasks.first()
+ val newestTask = freeformTasks.last()
+
+ val transition = Binder()
+ val wctCaptor = argumentCaptor<WindowContainerTransaction>()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenInstance(newestTask, freeformTasks[1].taskId)
+
+ val wct = wctCaptor.firstValue
+ verify(desksOrganizer).minimizeTask(wct, deskId, oldestTask)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
setUpLandscapeDisplay()
@@ -5853,7 +6649,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
InputMethod.TOUCH,
)
// Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
// Assert that task leash is updated via Surface Animations
verify(mReturnToDragStartAnimator)
@@ -5950,7 +6747,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
// Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
verify(mockToast).show()
}
@@ -6506,6 +7304,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() {
+ taskRepository.setDeskInactive(deskId = 0)
val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
@@ -6582,6 +7381,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() {
val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setDeskInactive(deskId = 0)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
assertThat(
@@ -6617,6 +7417,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() {
val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
+ taskRepository.setDeskInactive(deskId = 0)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
assertThat(
@@ -6641,12 +7442,21 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_invalidDisplay_dropsRequest() {
+ controller.createDesk(INVALID_DISPLAY)
+
+ verify(desksOrganizer, never()).createDesk(any(), any())
+ }
+
+ @Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() {
- val launchingTask = createFreeformTask()
+ taskRepository.setDeskInactive(deskId = 0)
+ val launchingTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
val wct = WindowContainerTransaction()
wct.reorder(launchingTask.token, /* onTop= */ true)
whenever(
@@ -6660,7 +7470,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
.thenReturn(Binder())
- controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
+ )
val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
val launchingTaskReorderIndex = latestWct.indexOfReorder(launchingTask, toTop = true)
@@ -6684,6 +7500,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
transitionType = TRANSIT_OPEN,
wct = WindowContainerTransaction(),
launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
)
verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(any())
@@ -6693,6 +7511,51 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun startLaunchTransition_launchingTaskFromInactiveDesk_otherDeskActive_activatesDesk() {
+ val activeDeskId = 4
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId)
+ val inactiveDesk = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk)
+ val launchingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk)
+ val transition = Binder()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(launchingTask.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ val wct = WindowContainerTransaction()
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = launchingTask.taskId,
+ deskId = inactiveDesk,
+ displayId = DEFAULT_DISPLAY,
+ )
+
+ verify(desksOrganizer).activateDesk(any(), eq(inactiveDesk))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ DeskTransition.ActivateDesk(
+ token = transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = inactiveDesk,
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() {
val wct = WindowContainerTransaction()
@@ -6707,8 +7570,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
.thenReturn(Binder())
- setUpFreeformTask()
- controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+ setUpFreeformTask(deskId = 0, displayId = DEFAULT_DISPLAY)
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
+ )
val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() })
@@ -6876,9 +7745,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
return task
}
- private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo =
- // active = false marks the task as non-visible; PiP window doesn't count as visible tasks
- setUpFreeformTask(active = false).apply {
+ private fun setUpPipTask(
+ autoEnterEnabled: Boolean,
+ displayId: Int = DEFAULT_DISPLAY,
+ deskId: Int = DEFAULT_DISPLAY,
+ ): RunningTaskInfo =
+ setUpFreeformTask(displayId = displayId, deskId = deskId).apply {
pictureInPictureParams =
PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
}
@@ -7019,7 +7891,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
): WindowContainerTransaction {
val arg = argumentCaptor<WindowContainerTransaction>()
verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
- .startTransition(arg.capture(), eq(currentBounds))
+ .startTransition(arg.capture(), eq(currentBounds), isNull())
return arg.lastValue
}
@@ -7057,7 +7929,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
- wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+ wct.changes.entries
+ .find { (token, change) ->
+ token == task.token.asBinder() &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0
+ }
+ ?.value
+ ?.configuration
+ ?.windowConfiguration
+ ?.bounds
private fun verifyWCTNotExecuted() {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 62e3c544e390..eeecb00b5b08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -39,12 +39,14 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -67,10 +69,13 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
import org.mockito.quality.Strictness
/**
@@ -84,6 +89,7 @@ import org.mockito.quality.Strictness
class DesktopTasksLimiterTest : ShellTestCase() {
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var desksOrganizer: DesksOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock lateinit var handler: Handler
@@ -128,6 +134,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
transitions,
userRepositories,
shellTaskOrganizer,
+ desksOrganizer,
MAX_TASK_LIMIT,
interactionJankMonitor,
mContext,
@@ -148,6 +155,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
transitions,
userRepositories,
shellTaskOrganizer,
+ desksOrganizer,
0,
interactionJankMonitor,
mContext,
@@ -163,6 +171,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
transitions,
userRepositories,
shellTaskOrganizer,
+ desksOrganizer,
-5,
interactionJankMonitor,
mContext,
@@ -172,6 +181,21 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ fun createDesktopTasksLimiter_withNoLimit_shouldSucceed() {
+ // Instantiation should succeed without an error.
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ maxTasksLimit = null,
+ interactionJankMonitor,
+ mContext,
+ handler,
+ )
+ }
+
+ @Test
fun addPendingMinimizeTransition_taskIsNotMinimized() {
desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
@@ -380,7 +404,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
- fun addAndGetMinimizeTaskChanges_tasksWithinLimit_noTaskMinimized() {
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksDisabled_noTaskMinimized() {
desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
@@ -388,7 +413,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
val minimizedTaskId =
desktopTasksLimiter.addAndGetMinimizeTaskChanges(
- displayId = DEFAULT_DISPLAY,
+ deskId = 0,
wct = wct,
newFrontTaskId = setUpFreeformTask().taskId,
)
@@ -398,7 +423,27 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
- fun addAndGetMinimizeTaskChanges_tasksAboveLimit_backTaskMinimized() {
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksEnabled_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
+
+ val wct = WindowContainerTransaction()
+ val minimizedTaskId =
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
+
+ assertThat(minimizedTaskId).isNull()
+ verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any())
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksDisabled_backTaskMinimized() {
desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
// The following list will be ordered bottom -> top, as the last task is moved to top last.
@@ -407,7 +452,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
val minimizedTaskId =
desktopTasksLimiter.addAndGetMinimizeTaskChanges(
- displayId = DEFAULT_DISPLAY,
+ deskId = DEFAULT_DISPLAY,
wct = wct,
newFrontTaskId = setUpFreeformTask().taskId,
)
@@ -419,7 +464,28 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
- fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_noTaskMinimized() {
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksEnabled_backTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ // The following list will be ordered bottom -> top, as the last task is moved to top last.
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+
+ val wct = WindowContainerTransaction()
+ val minimizedTaskId =
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = DEFAULT_DISPLAY,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
+
+ assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId)
+ verify(desksOrganizer).minimizeTask(wct, deskId = 0, tasks.first())
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksDisabled_noTaskMinimized() {
desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
@@ -428,7 +494,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
val minimizedTaskId =
desktopTasksLimiter.addAndGetMinimizeTaskChanges(
- displayId = 0,
+ deskId = 0,
wct = wct,
newFrontTaskId = setUpFreeformTask().taskId,
)
@@ -438,6 +504,26 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksEnabled_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
+
+ val wct = WindowContainerTransaction()
+ val minimizedTaskId =
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
+
+ assertThat(minimizedTaskId).isNull()
+ verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any())
+ }
+
+ @Test
fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
@@ -471,6 +557,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
transitions,
userRepositories,
shellTaskOrganizer,
+ desksOrganizer,
MAX_TASK_LIMIT2,
interactionJankMonitor,
mContext,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 5b0f94f91a13..5ef1ace7873d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -22,7 +22,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.Binder
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -30,7 +29,6 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IWindowContainerToken
@@ -41,22 +39,18 @@ import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
-import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
-import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
-import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -92,10 +86,10 @@ class DesktopTasksTransitionObserverTest {
private val userRepositories = mock<DesktopUserRepositories>()
private val taskRepository = mock<DesktopRepository>()
private val mixedHandler = mock<DesktopMixedTransitionHandler>()
+ private val pipTransitionObserver = mock<DesktopPipTransitionObserver>()
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
- private val desksTransitionObserver = mock<DesksTransitionObserver>()
private val wallpaperToken = MockToken().token()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
@@ -117,9 +111,9 @@ class DesktopTasksTransitionObserverTest {
transitions,
shellTaskOrganizer,
mixedHandler,
+ Optional.of(pipTransitionObserver),
backAnimationController,
desktopWallpaperActivityTokenProvider,
- desksTransitionObserver,
shellInit,
)
}
@@ -340,48 +334,47 @@ class DesktopTasksTransitionObserverTest {
}
@Test
- fun transitOpenWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(wallpaperTask),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
- }
-
- @Test
- fun transitToFrontWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun nonTopTransparentTaskOpened_clearTopTransparentTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ val nonTopTransparentTask = createTaskInfo(2)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
transitionObserver.onTransitionReady(
- transition = mock(),
- info = createToFrontTransition(wallpaperTask),
+ transition = mockTransition,
+ info = createOpenChangeTransition(nonTopTransparentTask),
startTransaction = mock(),
finishTransaction = mock(),
)
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
}
@Test
- fun transitToBackWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun nonTopTransparentTaskSentToFront_clearTopTransparentTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ val nonTopTransparentTask = createTaskInfo(2)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
transitionObserver.onTransitionReady(
- transition = mock(),
- info = createToBackTransition(wallpaperTask),
+ transition = mockTransition,
+ info = createToFrontTransition(nonTopTransparentTask),
startTransaction = mock(),
finishTransaction = mock(),
)
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = false, wallpaperTask.displayId)
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
}
@Test
@@ -398,71 +391,6 @@ class DesktopTasksTransitionObserverTest {
verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId)
}
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun pendingPipTransitionAborted_taskRepositoryOnPipAbortedInvoked() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- val pipTransition = Binder()
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
-
- transitionObserver.onTransitionReady(
- transition = pipTransition,
- info = createOpenChangeTransition(task, TRANSIT_PIP),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
- transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true)
-
- verify(taskRepository).onPipAborted(task.displayId, task.taskId)
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun exitPipTransition_taskRepositoryClearTaskInPip() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun removePipTransition_taskRepositoryClearTaskInPip() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
- }
-
- @Test
- fun onTransitionReady_forwardsToDesksTransitionObserver() {
- val transition = Binder()
- val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0)
-
- transitionObserver.onTransitionReady(
- transition = transition,
- info = info,
- StubTransaction(),
- StubTransaction(),
- )
-
- verify(desksTransitionObserver).onTransitionReady(transition, info)
- }
-
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index c00083b0607f..b511fc34fa89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ComponentName
import android.graphics.Rect
import android.view.Display.DEFAULT_DISPLAY
@@ -41,6 +42,18 @@ object DesktopTestHelpers {
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.setLastActiveTime(100)
+ .setUserId(DEFAULT_USER_ID)
+ .apply { bounds?.let { setBounds(it) } }
+ .build()
+
+ fun createPinnedTask(displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setParentTaskId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_PINNED)
+ .setLastActiveTime(100)
.apply { bounds?.let { setBounds(it) } }
.build()
@@ -50,6 +63,7 @@ object DesktopTestHelpers {
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setUserId(DEFAULT_USER_ID)
.setLastActiveTime(100)
/** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
@@ -63,6 +77,7 @@ object DesktopTestHelpers {
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setUserId(DEFAULT_USER_ID)
.setLastActiveTime(100)
.build()
@@ -72,6 +87,7 @@ object DesktopTestHelpers {
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_HOME)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setUserId(DEFAULT_USER_ID)
.setLastActiveTime(100)
.build()
@@ -91,4 +107,6 @@ object DesktopTestHelpers {
createSystemModalTask().apply {
baseActivity = ComponentName("com.test.dummypackage", "TestClass")
}
+
+ const val DEFAULT_USER_ID = 10
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index de55db86d1e7..4e2994c27729 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -11,6 +11,8 @@ import android.graphics.PointF
import android.graphics.Rect
import android.os.IBinder
import android.os.SystemProperties
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -23,6 +25,8 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -31,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleTransitions
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.CancelState
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -49,16 +54,19 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.MockitoSession
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -78,8 +86,18 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Mock private lateinit var homeTaskLeash: SurfaceControl
@Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var bubbleController: BubbleController
-
- private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
+ @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator
+ @Mock private lateinit var dragCancelCallback: Runnable
+ @Mock
+ private lateinit var dragToDesktopStateListener:
+ DragToDesktopTransitionHandler.DragToDesktopStateListener
+
+ private val transactionSupplier = Supplier {
+ val transaction = mock<SurfaceControl.Transaction>()
+ whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction)
+ whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction)
+ transaction
+ }
private lateinit var defaultHandler: DragToDesktopTransitionHandler
private lateinit var springHandler: SpringDragToDesktopTransitionHandler
@@ -97,7 +115,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
Optional.of(bubbleController),
transactionSupplier,
)
- .apply { setSplitScreenController(splitScreenController) }
+ .apply {
+ setSplitScreenController(splitScreenController)
+ dragToDesktopStateListener =
+ this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener
+ }
springHandler =
SpringDragToDesktopTransitionHandler(
context,
@@ -108,12 +130,24 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
Optional.of(bubbleController),
transactionSupplier,
)
- .apply { setSplitScreenController(splitScreenController) }
+ .apply {
+ setSplitScreenController(splitScreenController)
+ dragToDesktopStateListener =
+ this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener
+ }
mockitoSession =
ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
.mockStatic(SystemProperties::class.java)
.startMocking()
+ whenever(
+ transitions.startTransition(
+ eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+ /* wct= */ any(),
+ eq(defaultHandler),
+ )
+ )
+ .thenReturn(mock<IBinder>())
}
@After
@@ -423,7 +457,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
// No need to animate the cancel since the start animation couldn't even start.
- verifyZeroInteractions(dragAnimator)
+ verifyNoMoreInteractions(dragAnimator)
}
@Test
@@ -474,7 +508,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
// Should NOT have any transaction changes
- verifyZeroInteractions(mergedStartTransaction)
+ verifyNoMoreInteractions(mergedStartTransaction)
// Should NOT merge animation
verify(finishCallback, never()).onTransitionFinished(any())
}
@@ -675,17 +709,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
val startTransition = startDrag(defaultHandler, task)
val endTransition = mock<IBinder>()
defaultHandler.onTaskResizeAnimationListener = mock()
- defaultHandler.mergeAnimation(
+ mergeAnimation(
transition = endTransition,
- info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task,
- ),
- startT = mock<SurfaceControl.Transaction>(),
- finishT = mock<SurfaceControl.Transaction>(),
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ task = task,
mergeTarget = startTransition,
- finishCallback = mock<Transitions.TransitionFinishCallback>(),
)
defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock())
@@ -697,6 +725,185 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @DisableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_flagDisabled_cancelAndEndNotYetRequested_doesNotInterruptStartDrag() {
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(finishCallback, never()).onTransitionFinished(anyOrNull())
+ verify(dragAnimator, never()).cancelAnimator()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_interruptsStartDrag() {
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(dragAnimator).cancelAnimator()
+ verify(dragCancelCallback).run()
+ verify(dragToDesktopStateListener).onTransitionInterrupted()
+ assertThat(defaultHandler.inProgress).isTrue()
+ // Doesn't finish start transition yet
+ verify(finishCallback, never()).onTransitionFinished(/* wct= */ anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_finishesStartAfterAnimation() {
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+
+ verify(finishCallback).onTransitionFinished(/* wct= */ anyOrNull())
+ assertThat(defaultHandler.inProgress).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() {
+ val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ val startTransition =
+ startDrag(defaultHandler, task, finishCallback = startDragFinishCallback)
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeAnimation(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ task = task,
+ mergeTarget = startTransition,
+ )
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(startDragFinishCallback, never()).onTransitionFinished(anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startEndAnimation_otherTransitionInterruptedStartAfterEndRequest_finishImmediately() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ val endTransition =
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
+ val startTransaction = mock<SurfaceControl.Transaction>()
+ val endDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ val didAnimate =
+ defaultHandler.startAnimation(
+ transition = requireNotNull(endTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = startTransaction,
+ finishTransaction = mock(),
+ finishCallback = endDragFinishCallback,
+ )
+
+ assertThat(didAnimate).isTrue()
+ verify(startTransaction).apply()
+ verify(endDragFinishCallback).onTransitionFinished(anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startDrag_otherTransitionInterruptedStartAfterEndRequested_animatesDragWhenReady() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ verify(dragAnimator).startAnimation()
+ val endTransition =
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ defaultHandler.startAnimation(
+ transition = requireNotNull(endTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = mock(),
+ )
+
+ startDrag(defaultHandler, createTask())
+
+ verify(dragAnimator, times(2)).startAnimation()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startCancelAnimation_otherTransitionInterruptingAfterCancelRequest_finishImmediately() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ val cancelTransition =
+ cancelDragToDesktopTransition(defaultHandler, CancelState.STANDARD_CANCEL)
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ val cancelFinishCallback = mock<Transitions.TransitionFinishCallback>()
+ val startTransaction = mock<SurfaceControl.Transaction>()
+
+ val didAnimate =
+ defaultHandler.startAnimation(
+ transition = requireNotNull(cancelTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = startTransaction,
+ finishTransaction = mock(),
+ finishCallback = cancelFinishCallback,
+ )
+
+ assertThat(didAnimate).isTrue()
+ verify(startTransaction).apply()
+ verify(cancelFinishCallback).onTransitionFinished(/* wct= */ anyOrNull())
+ }
+
+ private fun mergeInterruptingTransition(mergeTarget: IBinder) {
+ defaultHandler.mergeAnimation(
+ transition = mock<IBinder>(),
+ info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = createTask()),
+ startT = mock(),
+ finishT = mock(),
+ mergeTarget = mergeTarget,
+ finishCallback = mock(),
+ )
+ }
+
+ private fun mergeAnimation(
+ transition: IBinder = mock(),
+ type: Int,
+ mergeTarget: IBinder,
+ task: RunningTaskInfo,
+ ) {
+ defaultHandler.mergeAnimation(
+ transition = transition,
+ info = createTransitionInfo(type = type, draggedTask = task),
+ startT = mock(),
+ finishT = mock(),
+ mergeTarget = mergeTarget,
+ finishCallback = mock(),
+ )
+ }
+
+ @Test
fun getAnimationFraction_returnsFraction() {
val fraction =
SpringDragToDesktopTransitionHandler.getAnimationFraction(
@@ -740,11 +947,48 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
assertThat(fraction).isWithin(TOLERANCE).of(0f)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun startDrag_indicatorFlagEnabled_attachesIndicatorToTransitionRoot() {
+ val task = createTask()
+ val rootLeash = mock<SurfaceControl>()
+ val startTransaction = mock<SurfaceControl.Transaction>()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ verify(visualIndicator).reparentLeash(startTransaction, rootLeash)
+ verify(visualIndicator).fadeInIndicator()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun startDrag_indicatorFlagDisabled_doesNotAttachIndicatorToTransitionRoot() {
+ val task = createTask()
+ val rootLeash = mock<SurfaceControl>()
+ val startTransaction = mock<SurfaceControl.Transaction>()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ verify(visualIndicator, never()).reparentLeash(any(), any())
+ verify(visualIndicator, never()).fadeInIndicator()
+ }
+
private fun startDrag(
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo = createTask(),
+ startTransaction: SurfaceControl.Transaction = mock(),
finishTransaction: SurfaceControl.Transaction = mock(),
homeChange: TransitionInfo.Change? = createHomeChange(),
+ transitionRootLeash: SurfaceControl = mock(),
+ finishCallback: Transitions.TransitionFinishCallback = mock(),
): IBinder {
whenever(dragAnimator.position).thenReturn(PointF())
// Simulate transition is started and is ready to animate.
@@ -756,10 +1000,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
draggedTask = task,
homeChange = homeChange,
+ rootLeash = transitionRootLeash,
),
- startTransaction = mock(),
+ startTransaction = startTransaction,
finishTransaction = finishTransaction,
- finishCallback = {},
+ finishCallback = finishCallback,
)
return transition
}
@@ -778,7 +1023,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task, dragAnimator)
+ handler.startDragToDesktopTransition(
+ task,
+ dragAnimator,
+ visualIndicator,
+ dragCancelCallback,
+ )
return token
}
@@ -845,6 +1095,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
type: Int,
draggedTask: RunningTaskInfo,
homeChange: TransitionInfo.Change? = createHomeChange(),
+ rootLeash: SurfaceControl = mock(),
) =
TransitionInfo(type, /* flags= */ 0).apply {
homeChange?.let { addChange(it) }
@@ -861,6 +1112,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
flags = flags or FLAG_IS_WALLPAPER
}
)
+ addRootLeash(draggedTask.displayId, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0)
}
private fun createHomeChange() =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
index 4c8cb3823f7e..3983bfbb2080 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
@@ -25,6 +25,7 @@ import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
@@ -49,9 +50,11 @@ import org.mockito.Mockito.mock
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/**
@@ -108,7 +111,7 @@ class VisualIndicatorViewContainerTest : ShellTestCase() {
eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
)
// Assert fadeIn, fadeOut, and animateIndicatorType were not called.
- verifyZeroInteractions(spyViewContainer)
+ verifyNoMoreInteractions(spyViewContainer)
}
@Test
@@ -121,7 +124,7 @@ class VisualIndicatorViewContainerTest : ShellTestCase() {
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
)
desktopExecutor.flushAll()
- verify(spyViewContainer).fadeInIndicator(any(), any(), any(), any())
+ verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any())
}
@Test
@@ -265,6 +268,35 @@ class VisualIndicatorViewContainerTest : ShellTestCase() {
)
}
+ @Test
+ fun fadeInIndicator_callsFadeIn() {
+ val spyViewContainer = setupSpyViewContainer()
+
+ spyViewContainer.fadeInIndicator(
+ mock<DisplayLayout>(),
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DEFAULT_DISPLAY,
+ )
+ desktopExecutor.flushAll()
+
+ verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any())
+ }
+
+ @Test
+ fun fadeInIndicator_alreadyReleased_doesntCallFadeIn() {
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.releaseVisualIndicator()
+
+ spyViewContainer.fadeInIndicator(
+ mock<DisplayLayout>(),
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DEFAULT_DISPLAY,
+ )
+ desktopExecutor.flushAll()
+
+ verify(spyViewContainer, never()).fadeInIndicatorInternal(any(), any(), any(), any())
+ }
+
private fun setupSpyViewContainer(): VisualIndicatorViewContainer {
val viewContainer =
VisualIndicatorViewContainer(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index 7560945856ec..dc973d0fda77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -90,7 +90,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(componentName)
desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
transitionHandler = createTransitionHandler()
- allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+ allowOverlayPermissionForAllUsers(arrayOf(SYSTEM_ALERT_WINDOW))
}
private fun createTransitionHandler() =
@@ -200,10 +200,16 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
.isTrue()
}
- fun allowOverlayPermission(permissions: Array<String>) {
+ fun allowOverlayPermissionForAllUsers(permissions: Array<String>) {
val packageInfo = mock<PackageInfo>()
packageInfo.requestedPermissions = permissions
- whenever(packageManager.getPackageInfo(anyString(), eq(PackageManager.GET_PERMISSIONS)))
+ whenever(
+ packageManager.getPackageInfoAsUser(
+ anyString(),
+ eq(PackageManager.GET_PERMISSIONS),
+ anyInt(),
+ )
+ )
.thenReturn(packageInfo)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
index 96b85ad2729e..9af504797182 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
@@ -15,20 +15,25 @@
*/
package com.android.wm.shell.desktopmode.multidesks
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change
import android.window.WindowContainerTransaction.HierarchyOp
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTaskOrganizer.TaskListener
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskMinimizationRoot
import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskRoot
@@ -36,14 +41,17 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertNotNull
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.kotlin.argThat
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Tests for [RootTaskDesksOrganizer].
@@ -58,57 +66,32 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
private val testShellInit = ShellInit(testExecutor)
private val mockShellCommandHandler = mock<ShellCommandHandler>()
private val mockShellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val launchAdjacentController = LaunchAdjacentController(mock())
private lateinit var organizer: RootTaskDesksOrganizer
@Before
fun setUp() {
organizer =
- RootTaskDesksOrganizer(testShellInit, mockShellCommandHandler, mockShellTaskOrganizer)
- }
-
- @Test
- fun testCreateDesk_callsBack() {
- val callback = FakeOnCreateCallback()
- organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
-
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
-
- assertThat(callback.created).isTrue()
- assertEquals(freeformRoot.taskId, callback.deskId)
+ RootTaskDesksOrganizer(
+ testShellInit,
+ mockShellCommandHandler,
+ mockShellTaskOrganizer,
+ launchAdjacentController,
+ )
}
- @Test
- fun testCreateDesk_createsMinimizationRoot() {
- val callback = FakeOnCreateCallback()
- organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
-
- val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
-
- val minimizationRoot = organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]
- assertNotNull(minimizationRoot)
- assertThat(minimizationRoot.deskId).isEqualTo(freeformRoot.taskId)
- assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId)
- }
+ @Test fun testCreateDesk_createsDeskAndMinimizationRoots() = runTest { createDesk() }
@Test
- fun testCreateMinimizationRoot_marksHidden() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
-
- val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+ fun testCreateMinimizationRoot_marksHidden() = runTest {
+ val desk = createDesk()
verify(mockShellTaskOrganizer)
.applyTransaction(
argThat { wct ->
wct.changes.any { change ->
- change.key == minimizationRootTask.token.asBinder() &&
+ change.key == desk.minimizationRoot.token.asBinder() &&
(change.value.changeMask and Change.CHANGE_HIDDEN != 0) &&
change.value.hidden
}
@@ -117,7 +100,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testOnTaskAppeared_withoutRequest_throws() {
+ fun testOnTaskAppeared_withoutRequest_throws() = runTest {
val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
assertThrows(Exception::class.java) {
@@ -126,41 +109,25 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testOnTaskAppeared_withRequestOnlyInAnotherDisplay_throws() {
- organizer.createDesk(displayId = 2, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask(Display.DEFAULT_DISPLAY).apply { parentTaskId = -1 }
-
- assertThrows(Exception::class.java) {
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
- }
- }
-
- @Test
- fun testOnTaskAppeared_duplicateRoot_throws() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ fun testOnTaskAppeared_duplicateRoot_throws() = runTest {
+ val desk = createDesk()
assertThrows(Exception::class.java) {
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ organizer.onTaskAppeared(desk.deskRoot.taskInfo, SurfaceControl())
}
}
@Test
- fun testOnTaskAppeared_duplicateMinimizedRoot_throws() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
- organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+ fun testOnTaskAppeared_duplicateMinimizedRoot_throws() = runTest {
+ val desk = createDesk()
assertThrows(Exception::class.java) {
- organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+ organizer.onTaskAppeared(desk.minimizationRoot.taskInfo, SurfaceControl())
}
}
@Test
- fun testOnTaskVanished_removesRoot() {
+ fun testOnTaskVanished_removesRoot() = runTest {
val desk = createDesk()
organizer.onTaskVanished(desk.deskRoot.taskInfo)
@@ -169,7 +136,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testOnTaskVanished_removesMinimizedRoot() {
+ fun testOnTaskVanished_removesMinimizedRoot() = runTest {
val desk = createDesk()
organizer.onTaskVanished(desk.deskRoot.taskInfo)
@@ -179,7 +146,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testDesktopWindowAppearsInDesk() {
+ fun testDesktopWindowAppearsInDesk() = runTest {
val desk = createDesk()
val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
@@ -189,7 +156,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testDesktopWindowAppearsInDeskMinimizationRoot() {
+ fun testDesktopWindowAppearsInDeskMinimizationRoot() = runTest {
val desk = createDesk()
val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
@@ -199,7 +166,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testDesktopWindowMovesToMinimizationRoot() {
+ fun testDesktopWindowMovesToMinimizationRoot() = runTest {
val desk = createDesk()
val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
organizer.onTaskAppeared(child, SurfaceControl())
@@ -212,7 +179,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testDesktopWindowDisappearsFromDesk() {
+ fun testDesktopWindowDisappearsFromDesk() = runTest {
val desk = createDesk()
val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
@@ -223,7 +190,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testDesktopWindowDisappearsFromDeskMinimizationRoot() {
+ fun testDesktopWindowDisappearsFromDeskMinimizationRoot() = runTest {
val desk = createDesk()
val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
@@ -234,7 +201,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testRemoveDesk_removesDeskRoot() {
+ fun testRemoveDesk_removesDeskRoot() = runTest {
val desk = createDesk()
val wct = WindowContainerTransaction()
@@ -250,7 +217,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testRemoveDesk_removesMinimizationRoot() {
+ fun testRemoveDesk_removesMinimizationRoot() = runTest {
val desk = createDesk()
val wct = WindowContainerTransaction()
@@ -266,7 +233,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testActivateDesk() {
+ fun testActivateDesk() = runTest {
val desk = createDesk()
val wct = WindowContainerTransaction()
@@ -290,7 +257,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testActivateDesk_didNotExist_throws() {
+ fun testActivateDesk_didNotExist_throws() = runTest {
val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
val wct = WindowContainerTransaction()
@@ -298,7 +265,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testMoveTaskToDesk() {
+ fun testMoveTaskToDesk() = runTest {
val desk = createDesk()
val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
@@ -324,7 +291,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testMoveTaskToDesk_didNotExist_throws() {
+ fun testMoveTaskToDesk_didNotExist_throws() = runTest {
val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
@@ -335,7 +302,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testGetDeskAtEnd() {
+ fun testGetDeskAtEnd() = runTest {
val desk = createDesk()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
@@ -348,7 +315,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testGetDeskAtEnd_inMinimizationRoot() {
+ fun testGetDeskAtEnd_inMinimizationRoot() = runTest {
val desk = createDesk()
val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
@@ -361,27 +328,24 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun testIsDeskActiveAtEnd() {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- freeformRoot.isVisibleRequested = true
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ fun testIsDeskActiveAtEnd() = runTest {
+ val desk = createDesk()
val isActive =
organizer.isDeskActiveAtEnd(
change =
- TransitionInfo.Change(freeformRoot.token, SurfaceControl()).apply {
- taskInfo = freeformRoot
+ TransitionInfo.Change(desk.deskRoot.token, SurfaceControl()).apply {
+ taskInfo = desk.deskRoot.taskInfo
mode = TRANSIT_TO_FRONT
},
- deskId = freeformRoot.taskId,
+ deskId = desk.deskRoot.deskId,
)
assertThat(isActive).isTrue()
}
@Test
- fun deactivateDesk_clearsLaunchRoot() {
+ fun deactivateDesk_clearsLaunchRoot() = runTest {
val wct = WindowContainerTransaction()
val desk = createDesk()
organizer.activateDesk(wct, desk.deskRoot.deskId)
@@ -400,7 +364,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun isDeskChange_forDeskId() {
+ fun isDeskChange_forDeskId() = runTest {
val desk = createDesk()
assertThat(
@@ -415,7 +379,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun isDeskChange_forDeskId_inMinimizationRoot() {
+ fun isDeskChange_forDeskId_inMinimizationRoot() = runTest {
val desk = createDesk()
assertThat(
@@ -433,7 +397,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun isDeskChange_anyDesk() {
+ fun isDeskChange_anyDesk() = runTest {
val desk = createDesk()
assertThat(
@@ -447,7 +411,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun isDeskChange_anyDesk_inMinimizationRoot() {
+ fun isDeskChange_anyDesk_inMinimizationRoot() = runTest {
val desk = createDesk()
assertThat(
@@ -464,7 +428,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun minimizeTask() {
+ fun minimizeTask() = runTest {
val desk = createDesk()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val wct = WindowContainerTransaction()
@@ -473,18 +437,11 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task)
- assertThat(
- wct.hierarchyOps.any { hop ->
- hop.isReparent &&
- hop.container == task.token.asBinder() &&
- hop.newParent == desk.minimizationRoot.token.asBinder()
- }
- )
- .isTrue()
+ assertThat(wct.hasMinimizationHops(desk, task.token)).isTrue()
}
@Test
- fun minimizeTask_alreadyMinimized_noOp() {
+ fun minimizeTask_alreadyMinimized_noOp() = runTest {
val desk = createDesk()
val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
val wct = WindowContainerTransaction()
@@ -496,7 +453,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
- fun minimizeTask_inDifferentDesk_noOp() {
+ fun minimizeTask_inDifferentDesk_noOp() = runTest {
val desk = createDesk()
val otherDesk = createDesk()
val task = createFreeformTask().apply { parentTaskId = otherDesk.deskRoot.deskId }
@@ -508,30 +465,251 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
assertThat(wct.isEmpty).isTrue()
}
+ @Test
+ fun unminimizeTask() = runTest {
+ val desk = createDesk()
+ val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
+ val wct = WindowContainerTransaction()
+ organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task)
+ organizer.onTaskAppeared(task, SurfaceControl())
+ organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+ task.parentTaskId = desk.minimizationRoot.rootId
+ organizer.onTaskInfoChanged(task)
+
+ wct.clear()
+ organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+
+ assertThat(wct.hasUnminimizationHops(desk, task.token)).isTrue()
+ }
+
+ @Test
+ fun unminimizeTask_alreadyUnminimized_noOp() = runTest {
+ val desk = createDesk()
+ val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
+ val wct = WindowContainerTransaction()
+ organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task)
+ organizer.onTaskAppeared(task, SurfaceControl())
+
+ wct.clear()
+ organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+
+ assertThat(wct.hasUnminimizationHops(desk, task.token)).isFalse()
+ }
+
+ @Test
+ fun unminimizeTask_notInDesk_noOp() = runTest {
+ val desk = createDesk()
+ val task = createFreeformTask()
+ val wct = WindowContainerTransaction()
+
+ organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+
+ assertThat(wct.hasUnminimizationHops(desk, task.token)).isFalse()
+ }
+
+ @Test
+ fun reorderTaskToFront() = runTest {
+ val desk = createDesk()
+ val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
+ val wct = WindowContainerTransaction()
+ organizer.onTaskAppeared(task, SurfaceControl())
+
+ organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task)
+
+ assertThat(
+ wct.hierarchyOps.singleOrNull { hop ->
+ hop.container == task.token.asBinder() &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop &&
+ hop.includingParents()
+ }
+ )
+ .isNotNull()
+ }
+
+ @Test
+ fun reorderTaskToFront_notInDesk_noOp() = runTest {
+ val desk = createDesk()
+ val task = createFreeformTask()
+ val wct = WindowContainerTransaction()
+
+ organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task)
+
+ assertThat(
+ wct.hierarchyOps.singleOrNull { hop ->
+ hop.container == task.token.asBinder() &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop &&
+ hop.includingParents()
+ }
+ )
+ .isNull()
+ }
+
+ @Test
+ fun reorderTaskToFront_minimized_unminimizesAndReorders() = runTest {
+ val desk = createDesk()
+ val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
+ val wct = WindowContainerTransaction()
+ organizer.onTaskAppeared(task, SurfaceControl())
+ task.parentTaskId = desk.minimizationRoot.rootId
+ organizer.onTaskInfoChanged(task)
+
+ organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task)
+
+ assertThat(wct.hasUnminimizationHops(desk, task.token)).isTrue()
+ assertThat(
+ wct.hierarchyOps.singleOrNull { hop ->
+ hop.container == task.token.asBinder() &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop &&
+ hop.includingParents()
+ }
+ )
+ .isNotNull()
+ }
+
+ @Test
+ fun onTaskAppeared_visibleDesk_onlyDesk_disablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = true
+
+ createDesk(visible = true)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
+ }
+
+ @Test
+ fun onTaskAppeared_invisibleDesk_onlyDesk_enablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = false
+
+ createDesk(visible = false)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue()
+ }
+
+ @Test
+ fun onTaskAppeared_invisibleDesk_otherVisibleDesk_disablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = true
+
+ createDesk(visible = true)
+ createDesk(visible = false)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
+ }
+
+ @Test
+ fun onTaskInfoChanged_deskBecomesVisible_onlyDesk_disablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = true
+
+ val desk = createDesk(visible = false)
+ desk.deskRoot.taskInfo.isVisible = true
+ organizer.onTaskInfoChanged(desk.deskRoot.taskInfo)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
+ }
+
+ @Test
+ fun onTaskInfoChanged_deskBecomesInvisible_onlyDesk_enablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = false
+
+ val desk = createDesk(visible = true)
+ desk.deskRoot.taskInfo.isVisible = false
+ organizer.onTaskInfoChanged(desk.deskRoot.taskInfo)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue()
+ }
+
+ @Test
+ fun onTaskInfoChanged_deskBecomesInvisible_otherVisibleDesk_disablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = true
+
+ createDesk(visible = true)
+ val desk = createDesk(visible = true)
+ desk.deskRoot.taskInfo.isVisible = false
+ organizer.onTaskInfoChanged(desk.deskRoot.taskInfo)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
+ }
+
+ @Test
+ fun onTaskVanished_visibleDeskDisappears_onlyDesk_enablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = false
+
+ val desk = createDesk(visible = true)
+ organizer.onTaskVanished(desk.deskRoot.taskInfo)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue()
+ }
+
+ @Test
+ fun onTaskVanished_visibleDeskDisappears_otherDeskVisible_disablesLaunchAdjacent() = runTest {
+ launchAdjacentController.launchAdjacentEnabled = true
+
+ createDesk(visible = true)
+ val desk = createDesk(visible = true)
+ organizer.onTaskVanished(desk.deskRoot.taskInfo)
+
+ assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
+ }
+
private data class DeskRoots(
val deskRoot: DeskRoot,
val minimizationRoot: DeskMinimizationRoot,
)
- private fun createDesk(): DeskRoots {
- organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
- val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(freeformRoot, SurfaceControl())
- val minimizationRoot = createFreeformTask().apply { parentTaskId = -1 }
- organizer.onTaskAppeared(minimizationRoot, SurfaceControl())
- return DeskRoots(
- organizer.deskRootsByDeskId[freeformRoot.taskId],
- checkNotNull(organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]),
- )
- }
-
- private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback {
- var deskId: Int? = null
- val created: Boolean
- get() = deskId != null
-
- override fun onCreated(deskId: Int) {
- this.deskId = deskId
- }
+ private suspend fun createDesk(visible: Boolean = true): DeskRoots {
+ val freeformRootTask =
+ createFreeformTask().apply {
+ parentTaskId = -1
+ isVisible = visible
+ isVisibleRequested = visible
+ }
+ val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
+ Mockito.reset(mockShellTaskOrganizer)
+ whenever(
+ mockShellTaskOrganizer.createRootTask(
+ Display.DEFAULT_DISPLAY,
+ WINDOWING_MODE_FREEFORM,
+ organizer,
+ true,
+ )
+ )
+ .thenAnswer { invocation ->
+ val listener = (invocation.arguments[2] as TaskListener)
+ listener.onTaskAppeared(freeformRootTask, SurfaceControl())
+ }
+ .thenAnswer { invocation ->
+ val listener = (invocation.arguments[2] as TaskListener)
+ listener.onTaskAppeared(minimizationRootTask, SurfaceControl())
+ }
+ val deskId = organizer.createDesk(Display.DEFAULT_DISPLAY)
+ assertEquals(freeformRootTask.taskId, deskId)
+ val deskRoot = assertNotNull(organizer.deskRootsByDeskId.get(freeformRootTask.taskId))
+ val minimizationRoot =
+ assertNotNull(organizer.deskMinimizationRootsByDeskId[freeformRootTask.taskId])
+ assertThat(minimizationRoot.deskId).isEqualTo(freeformRootTask.taskId)
+ assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId)
+ return DeskRoots(deskRoot, minimizationRoot)
}
+
+ private fun WindowContainerTransaction.hasMinimizationHops(
+ desk: DeskRoots,
+ task: WindowContainerToken,
+ ): Boolean =
+ hierarchyOps.any { hop ->
+ hop.isReparent &&
+ hop.container == task.asBinder() &&
+ hop.newParent == desk.minimizationRoot.token.asBinder()
+ }
+
+ private fun WindowContainerTransaction.hasUnminimizationHops(
+ desk: DeskRoots,
+ task: WindowContainerToken,
+ ): Boolean =
+ hierarchyOps.any { hop ->
+ hop.isReparent &&
+ hop.container == task.asBinder() &&
+ hop.newParent == desk.deskRoot.token.asBinder() &&
+ hop.toTop
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
index dd9e6ca0deae..4440d4e801fe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.desktopmode.persistence
import android.os.UserManager
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -82,10 +81,27 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
- /** TODO: b/362720497 - add multi-desk version when implemented. */
- @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun initWithPersistence_multipleUsers_addedCorrectly_multiDesksDisabled() =
+ fun init_updatesFlow() =
+ runTest(StandardTestDispatcher()) {
+ whenever(persistentRepository.getUserDesktopRepositoryMap())
+ .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1))
+ whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1))
+ .thenReturn(desktopRepositoryState1)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2)
+
+ repositoryInitializer.initialize(desktopUserRepositories)
+
+ assertThat(repositoryInitializer.isInitialized.value).isTrue()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_HSUM,
+ FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun initWithPersistence_multipleUsers_addedCorrectly() =
runTest(StandardTestDispatcher()) {
whenever(persistentRepository.getUserDesktopRepositoryMap())
.thenReturn(
@@ -104,50 +120,74 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
repositoryInitializer.initialize(desktopUserRepositories)
- // Desktop Repository currently returns all tasks across desktops for a specific user
- // since the repository currently doesn't handle desktops. This test logic should be
- // updated
- // once the repository handles multiple desktops.
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getActiveTaskIdsInDesk(DESKTOP_ID_1)
)
- .containsExactly(1, 3, 4, 5)
+ .containsExactly(1, 3)
.inOrder()
assertThat(
desktopUserRepositories
.getProfile(USER_ID_1)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ .getActiveTaskIdsInDesk(DESKTOP_ID_2)
)
- .containsExactly(5, 1)
+ .containsExactly(4, 5)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_2)
+ .getActiveTaskIdsInDesk(DESKTOP_ID_3)
)
- .containsExactly(3, 4)
+ .containsExactly(7, 8)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_1)
+ )
+ .containsExactly(1)
.inOrder()
-
assertThat(
- desktopUserRepositories.getProfile(USER_ID_2).getActiveTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_2)
)
- .containsExactly(7, 8)
+ .containsExactly(5)
.inOrder()
assertThat(
desktopUserRepositories
.getProfile(USER_ID_2)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_3)
+ )
+ .containsExactly(7)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getMinimizedTaskIdsInDesk(DESKTOP_ID_1)
+ )
+ .containsExactly(3)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getMinimizedTaskIdsInDesk(DESKTOP_ID_2)
)
- .contains(7)
+ .containsExactly(4)
+ .inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_2).getMinimizedTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_2)
+ .getMinimizedTaskIdsInDesk(DESKTOP_ID_3)
)
.containsExactly(8)
+ .inOrder()
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
- /** TODO: b/362720497 - add multi-desk version when implemented. */
- @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun initWithPersistence_singleUser_addedCorrectly_multiDesksDisabled() =
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun initWithPersistence_singleUser_addedCorrectly() =
runTest(StandardTestDispatcher()) {
whenever(persistentRepository.getUserDesktopRepositoryMap())
.thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1))
@@ -161,23 +201,44 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
assertThat(
desktopUserRepositories
.getProfile(USER_ID_1)
- .getActiveTaskIdsInDesk(deskId = DEFAULT_DISPLAY)
+ .getActiveTaskIdsInDesk(DESKTOP_ID_1)
+ )
+ .containsExactly(1, 3)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getActiveTaskIdsInDesk(DESKTOP_ID_2)
+ )
+ .containsExactly(4, 5)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_1)
+ )
+ .containsExactly(1)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_2)
)
- .containsExactly(1, 3, 4, 5)
+ .containsExactly(5)
.inOrder()
assertThat(
desktopUserRepositories
.getProfile(USER_ID_1)
- .getExpandedTasksIdsInDeskOrdered(deskId = DEFAULT_DISPLAY)
+ .getMinimizedTaskIdsInDesk(DESKTOP_ID_1)
)
- .containsExactly(5, 1)
+ .containsExactly(3)
.inOrder()
assertThat(
desktopUserRepositories
.getProfile(USER_ID_1)
- .getMinimizedTaskIdsInDesk(deskId = DEFAULT_DISPLAY)
+ .getMinimizedTaskIdsInDesk(DESKTOP_ID_2)
)
- .containsExactly(3, 4)
+ .containsExactly(4)
.inOrder()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 9509aaf20c9b..52c5ad1f8e49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -41,6 +41,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -198,6 +199,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
public void visibilityTaskChanged_visible_setLaunchAdjacentDisabled() {
ActivityManager.RunningTaskInfo task =
new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
@@ -209,6 +211,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
public void visibilityTaskChanged_notVisible_setLaunchAdjacentEnabled() {
ActivityManager.RunningTaskInfo task =
new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index bc918450a3cf..714e5f486285 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -44,10 +44,12 @@ import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.StubTransaction;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
@@ -68,6 +70,7 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase {
@Mock private WindowDecorViewModel mWindowDecorViewModel;
@Mock private TaskChangeListener mTaskChangeListener;
@Mock private FocusTransitionObserver mFocusTransitionObserver;
+ @Mock private DesksTransitionObserver mDesksTransitionObserver;
private FreeformTaskTransitionObserver mTransitionObserver;
@@ -88,7 +91,8 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase {
Optional.of(mDesktopImmersiveController),
mWindowDecorViewModel,
Optional.of(mTaskChangeListener),
- mFocusTransitionObserver);
+ mFocusTransitionObserver,
+ Optional.of(mDesksTransitionObserver));
final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), same(mTransitionObserver));
@@ -357,6 +361,18 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase {
verify(mDesktopImmersiveController).onTransitionFinished(transition, /* aborted= */ false);
}
+ @Test
+ public void onTransitionReady_forwardsToDesksTransitionObserver() {
+ final IBinder transition = mock(IBinder.class);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, /* flags= */ 0)
+ .build();
+
+ mTransitionObserver.onTransitionReady(transition, info, new StubTransaction(),
+ new StubTransaction());
+
+ verify(mDesksTransitionObserver).onTransitionReady(transition, info);
+ }
+
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = taskId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
index 14f9ffc52a66..2bd9afcef1bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -107,7 +107,7 @@ public class PipAlphaAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
index 72c466663a56..fa7ab9521dac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -117,7 +117,7 @@ public class PipEnterAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
// Check corner and shadow radii were set
verify(mMockAnimateTransaction, atLeastOnce())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
index b816f0ef041e..97133bedfa2d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -143,7 +143,7 @@ public class PipExpandAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
index 23fbad05ec99..c99ca6dd7065 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
@@ -118,7 +118,7 @@ public class PipResizeAnimatorTest {
});
verify(mMockStartCallback).run();
- verifyZeroInteractions(mMockEndCallback);
+ verifyNoMoreInteractions(mMockEndCallback);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java
new file mode 100644
index 000000000000..9c0127ea2414
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pip2.phone;
+
+import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
+import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP;
+import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.kotlin.VerificationKt.times;
+
+import android.content.Context;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipInteractionHandler}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipInteractionHandlerTest {
+ @Mock private Context mMockContext;
+ @Mock private Handler mMockHandler;
+ @Mock private InteractionJankMonitor mMockInteractionJankMonitor;
+
+ private SurfaceControl mTestLeash;
+
+ private PipInteractionHandler mPipInteractionHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mPipInteractionHandler = new PipInteractionHandler(mMockContext, mMockHandler,
+ mMockInteractionJankMonitor);
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipInteractionHandlerTest")
+ .setCallsite("PipInteractionHandlerTest")
+ .build();
+ }
+
+ @Test
+ public void begin_expand_startsTracking() {
+ mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP);
+
+ verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
+ eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
+ eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP)));
+ }
+
+ @Test
+ public void begin_expandToSplit_startsTracking() {
+ mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP_TO_SPLIT);
+
+ verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
+ eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
+ eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP_TO_SPLIT)));
+ }
+
+ @Test
+ public void end_stopsTracking() {
+ mPipInteractionHandler.end();
+
+ verify(mMockInteractionJankMonitor, times(1)).end(CUJ_PIP_TRANSITION);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index 275e4882a79d..42f65dd71f16 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.phone;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -86,6 +87,7 @@ public class PipSchedulerTest {
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
@Mock private SplitScreenController mMockSplitScreenController;
+ @Mock private SurfaceControl mMockLeash;
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -315,6 +317,30 @@ public class PipSchedulerTest {
verify(mMockAlphaAnimator, never()).start();
}
+ @Test
+ public void onPipTransitionStateChanged_exiting_endAnimation() {
+ mPipScheduler.setOverlayFadeoutAnimator(mMockAlphaAnimator);
+ when(mMockAlphaAnimator.isStarted()).thenReturn(true);
+ mPipScheduler.onPipTransitionStateChanged(PipTransitionState.ENTERED_PIP,
+ PipTransitionState.EXITING_PIP, null);
+
+ verify(mMockAlphaAnimator, times(1)).end();
+ assertNull("mOverlayFadeoutAnimator should be reset to null",
+ mPipScheduler.getOverlayFadeoutAnimator());
+ }
+
+ @Test
+ public void onPipTransitionStateChanged_scheduledBoundsChange_endAnimation() {
+ mPipScheduler.setOverlayFadeoutAnimator(mMockAlphaAnimator);
+ when(mMockAlphaAnimator.isStarted()).thenReturn(true);
+ mPipScheduler.onPipTransitionStateChanged(PipTransitionState.ENTERED_PIP,
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE, null);
+
+ verify(mMockAlphaAnimator, times(1)).end();
+ assertNull("mOverlayFadeoutAnimator should be reset to null",
+ mPipScheduler.getOverlayFadeoutAnimator());
+ }
+
private void setNullPipTaskToken() {
when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
index 333569a7206e..b6894fd0a9fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
@@ -24,18 +24,21 @@ 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.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
import static org.mockito.kotlin.VerificationKt.clearInvocations;
import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;
-import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions;
+import static org.mockito.kotlin.VerificationKt.verifyNoMoreInteractions;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.RemoteAction;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -48,8 +51,10 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import org.junit.Before;
@@ -107,6 +112,16 @@ public class PipTaskListenerTest {
}
@Test
+ public void constructor_addOnPipComponentChangedListener() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+
+ verify(mMockPipBoundsState).addOnPipComponentChangedListener(
+ any(PipBoundsState.OnPipComponentChangedListener.class));
+ }
+
+ @Test
public void setPictureInPictureParams_updatePictureInPictureParams() {
mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
@@ -161,7 +176,7 @@ public class PipTaskListenerTest {
mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
aspectRatio, action1));
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
}
@Test
@@ -178,7 +193,7 @@ public class PipTaskListenerTest {
clearInvocations(mMockPipParamsChangedCallback);
mPipTaskListener.onTaskInfoChanged(new ActivityManager.RunningTaskInfo());
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
verify(mMockPipTransitionState, times(0))
.setOnIdlePipTransitionStateRunnable(any(Runnable.class));
}
@@ -230,7 +245,7 @@ public class PipTaskListenerTest {
mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class));
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
}
@Test
@@ -247,7 +262,7 @@ public class PipTaskListenerTest {
clearInvocations(mMockPipParamsChangedCallback);
mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
- verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verifyNoMoreInteractions(mMockPipParamsChangedCallback);
verify(mMockPipTransitionState, times(0))
.setOnIdlePipTransitionStateRunnable(any(Runnable.class));
}
@@ -304,7 +319,7 @@ public class PipTaskListenerTest {
PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
extras);
- verifyZeroInteractions(mMockPipScheduler);
+ verifyNoMoreInteractions(mMockPipScheduler);
}
@Test
@@ -359,6 +374,26 @@ public class PipTaskListenerTest {
verify(mMockPipResizeAnimator, times(0)).start();
}
+ @Test
+ public void onPipComponentChanged_clearPictureInPictureParams() {
+ when(mMockContext.getResources()).thenReturn(mock(Resources.class));
+ PipBoundsState pipBoundsState = new PipBoundsState(mMockContext,
+ mock(PhoneSizeSpecSource.class), mock(PipDisplayLayoutState.class));
+ pipBoundsState.setLastPipComponentName(new ComponentName("org.test", "test1"));
+
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, pipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ Rational aspectRatio = new Rational(4, 3);
+ String action1 = "action1";
+ mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+ aspectRatio, action1));
+
+ pipBoundsState.setLastPipComponentName(new ComponentName("org.test", "test2"));
+
+ assertTrue(mPipTaskListener.getPictureInPictureParams().empty());
+ }
+
private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio,
String... actions) {
final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
index fa9b5903bc3c..66b8ef1ac130 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
@@ -16,12 +16,15 @@
package com.android.wm.shell.pip2.phone;
+import static org.mockito.Mockito.when;
+
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PipDesktopState;
import junit.framework.Assert;
@@ -45,9 +48,12 @@ public class PipTransitionStateTest extends ShellTestCase {
@Mock
private Handler mMainHandler;
+ @Mock
+ private PipDesktopState mMockPipDesktopState;
+
@Before
public void setUp() {
- mPipTransitionState = new PipTransitionState(mMainHandler);
+ mPipTransitionState = new PipTransitionState(mMainHandler, mMockPipDesktopState);
mPipTransitionState.setState(PipTransitionState.UNDEFINED);
mEmptyParcelable = new Bundle();
}
@@ -128,4 +134,29 @@ public class PipTransitionStateTest extends ShellTestCase {
mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
Assert.assertEquals(PipTransitionState.EXITING_PIP, mPipTransitionState.getState());
}
+
+ @Test
+ public void testShouldTransitionToState_scheduledBoundsChange_inPip_returnsTrue() {
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+
+ Assert.assertTrue(mPipTransitionState.shouldTransitionToState(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE));
+ }
+
+ @Test
+ public void testShouldTransitionToState_scheduledBoundsChange_notInPip_returnsFalse() {
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+
+ Assert.assertFalse(mPipTransitionState.shouldTransitionToState(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE));
+ }
+
+ @Test
+ public void testShouldTransitionToState_scheduledBoundsChange_dragToDesktop_returnsFalse() {
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ when(mMockPipDesktopState.isDragToDesktopInProgress()).thenReturn(true);
+
+ Assert.assertFalse(mPipTransitionState.shouldTransitionToState(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE));
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
index 82cdfd52d2db..51de50da6921 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Flags;
@@ -82,7 +82,7 @@ public class PipUiStateChangeControllerTests {
mPipUiStateChangeController.onPipTransitionStateChanged(
PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERING_PIP, Bundle.EMPTY);
- verifyZeroInteractions(mPictureInPictureUiStateConsumer);
+ verifyNoMoreInteractions(mPictureInPictureUiStateConsumer);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
index 2a22842eda1a..cc66f00525b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
@@ -23,6 +23,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -48,11 +49,13 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.TransitionInfoBuilder;
@@ -61,6 +64,8 @@ import com.android.wm.shell.util.StubTransaction;
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;
@@ -79,6 +84,7 @@ public class PipExpandHandlerTest {
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipTransitionState mMockPipTransitionState;
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private PipInteractionHandler mMockPipInteractionHandler;
@Mock private SplitScreenController mMockSplitScreenController;
@Mock private IBinder mMockTransitionToken;
@@ -89,6 +95,8 @@ public class PipExpandHandlerTest {
@Mock private PipExpandAnimator mMockPipExpandAnimator;
+ @Captor private ArgumentCaptor<Runnable> mAnimatorCallbackArgumentCaptor;
+
@Surface.Rotation
private static final int DISPLAY_ROTATION = Surface.ROTATION_0;
@@ -108,7 +116,7 @@ public class PipExpandHandlerTest {
mPipExpandHandler = new PipExpandHandler(mMockContext, mMockPipBoundsState,
mMockPipBoundsAlgorithm, mMockPipTransitionState, mMockPipDisplayLayoutState,
- Optional.of(mMockSplitScreenController));
+ mMockPipInteractionHandler, Optional.of(mMockSplitScreenController));
mPipExpandHandler.setPipExpandAnimatorSupplier((context, leash, startTransaction,
finishTransaction, baseBounds, startBounds, endBounds,
sourceRectHint, rotation) -> mMockPipExpandAnimator);
@@ -138,6 +146,13 @@ public class PipExpandHandlerTest {
verify(mMockPipExpandAnimator, times(1)).start();
verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+
+ verify(mMockPipExpandAnimator, times(1))
+ .setAnimationStartCallback(mAnimatorCallbackArgumentCaptor.capture());
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(mAnimatorCallbackArgumentCaptor.getValue());
+ verify(mMockPipInteractionHandler, times(1)).begin(any(),
+ eq(PipInteractionHandler.INTERACTION_EXIT_PIP));
}
@Test
@@ -158,6 +173,13 @@ public class PipExpandHandlerTest {
verify(mMockSplitScreenController, times(1)).finishEnterSplitScreen(eq(mFinishT));
verify(mMockPipExpandAnimator, times(1)).start();
verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+
+ verify(mMockPipExpandAnimator, times(1))
+ .setAnimationStartCallback(mAnimatorCallbackArgumentCaptor.capture());
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(mAnimatorCallbackArgumentCaptor.getValue());
+ verify(mMockPipInteractionHandler, times(1)).begin(any(),
+ eq(PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
}
private TransitionInfo getExpandFromPipTransitionInfo(@WindowManager.TransitionType int type,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 6ecebd76a951..75f6bda4d750 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -203,7 +203,7 @@ class GroupedTaskInfoTest : ShellTestCase() {
assertThat(taskInfoFromParcel.taskInfoList).hasSize(3)
// Only compare task ids
val taskIdComparator = Correspondence.transforming<TaskInfo, Int>(
- { it?.taskId }, "has taskId of"
+ { it.taskId }, "has taskId of"
)
assertThat(taskInfoFromParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
.containsExactly(1, 2, 3).inOrder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
index fd22a84dee5d..2b39262d9f00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt
@@ -53,7 +53,8 @@ class DragZoneFactoryTest {
tabletPortrait.copy(windowBounds = Rect(0, 0, 800, 900), isSmallTablet = true)
private val foldableLandscape =
foldablePortrait.copy(windowBounds = Rect(0, 0, 900, 800), isLandscape = true)
- private val splitScreenModeChecker = SplitScreenModeChecker { SplitScreenMode.NONE }
+ private var splitScreenMode = SplitScreenMode.NONE
+ private val splitScreenModeChecker = SplitScreenModeChecker { splitScreenMode }
private var isDesktopWindowModeSupported = true
private val desktopWindowModeChecker = DesktopWindowModeChecker { isDesktopWindowModeSupported }
@@ -283,7 +284,7 @@ class DragZoneFactoryTest {
}
@Test
- fun dragZonesForBubble_tablet_desktopModeDisabled() {
+ fun dragZonesForBubble_desktopModeDisabled() {
isDesktopWindowModeSupported = false
dragZoneFactory =
DragZoneFactory(
@@ -298,7 +299,7 @@ class DragZoneFactoryTest {
}
@Test
- fun dragZonesForExpandedView_tablet_desktopModeDisabled() {
+ fun dragZonesForExpandedView_desktopModeDisabled() {
isDesktopWindowModeSupported = false
dragZoneFactory =
DragZoneFactory(
@@ -314,6 +315,38 @@ class DragZoneFactoryTest {
assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty()
}
+ @Test
+ fun dragZonesForBubble_splitScreenModeUnsupported() {
+ splitScreenMode = SplitScreenMode.UNSUPPORTED
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
+ val dragZones =
+ dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT))
+ assertThat(dragZones.filterIsInstance<DragZone.Split>()).isEmpty()
+ }
+
+ @Test
+ fun dragZonesForExpandedView_splitScreenModeUnsupported() {
+ splitScreenMode = SplitScreenMode.UNSUPPORTED
+ dragZoneFactory =
+ DragZoneFactory(
+ context,
+ foldableLandscape,
+ splitScreenModeChecker,
+ desktopWindowModeChecker
+ )
+ val dragZones =
+ dragZoneFactory.createSortedDragZones(
+ DraggedObject.ExpandedView(BubbleBarLocation.LEFT)
+ )
+ assertThat(dragZones.filterIsInstance<DragZone.Split>()).isEmpty()
+ }
+
private inline fun <reified T> verifyInstance(): DragZoneVerifier = { dragZone ->
assertThat(dragZone).isInstanceOf(T::class.java)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
index 3f7f21ef2074..3b21e365e911 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -65,7 +65,7 @@ class DropTargetManagerTest {
container = FrameLayout(context)
dragZoneChangedListener = FakeDragZoneChangedListener()
dropTargetManager =
- DropTargetManager(context, container, isLayoutRtl = false, dragZoneChangedListener)
+ DropTargetManager(context, container, dragZoneChangedListener)
}
@Test
@@ -228,6 +228,22 @@ class DropTargetManagerTest {
}
@Test
+ fun onDragEnded_dropTargetNotifies() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
+ )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ dropTargetManager.onDragEnded()
+ }
+ assertThat(dragZoneChangedListener.endedDragZone).isEqualTo(bubbleRightDragZone)
+ }
+
+ @Test
fun startNewDrag_beforeDropTargetRemoved() {
dropTargetManager.onDragStarted(
DraggedObject.Bubble(BubbleBarLocation.LEFT),
@@ -330,14 +346,19 @@ class DropTargetManagerTest {
var initialDragZone: DragZone? = null
var fromDragZone: DragZone? = null
var toDragZone: DragZone? = null
+ var endedDragZone: DragZone? = null
override fun onInitialDragZoneSet(dragZone: DragZone) {
initialDragZone = dragZone
}
- override fun onDragZoneChanged(from: DragZone, to: DragZone) {
+ override fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone) {
fromDragZone = from
toDragZone = to
}
+
+ override fun onDragEnded(zone: DragZone) {
+ endedDragZone = zone
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 5ac680048a7e..12785c03aa9f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -42,6 +42,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
@@ -87,7 +88,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() {
- allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+ allowOverlayPermissionForAllUsers(arrayOf(SYSTEM_ALERT_WINDOW))
assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -101,7 +102,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() {
- allowOverlayPermission(arrayOf())
+ allowOverlayPermissionForAllUsers(arrayOf())
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -115,7 +116,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptCachedPermissionCheckIsUsed() {
- allowOverlayPermission(arrayOf())
+ allowOverlayPermissionForAllUsers(arrayOf())
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -123,6 +124,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
isTopActivityNoDisplay = false
numActivities = 1
baseActivity = baseActivityTest
+ userId = 10
}))
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
@@ -131,10 +133,26 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
isTopActivityNoDisplay = false
numActivities = 1
baseActivity = baseActivityTest
+ userId = 10
}))
- verify(packageManager, times(1)).getPackageInfo(
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = baseActivityTest
+ userId = 0
+ }))
+ verify(packageManager, times(1)).getPackageInfoAsUser(
+ eq("com.test.dummypackage"),
+ eq(PackageManager.GET_PERMISSIONS),
+ eq(10)
+ )
+ verify(packageManager, times(1)).getPackageInfoAsUser(
eq("com.test.dummypackage"),
- eq(PackageManager.GET_PERMISSIONS)
+ eq(PackageManager.GET_PERMISSIONS),
+ eq(0)
)
}
@@ -284,13 +302,14 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
}
}
- fun allowOverlayPermission(permissions: Array<String>) {
+ fun allowOverlayPermissionForAllUsers(permissions: Array<String>) {
val packageInfo = mock<PackageInfo>()
packageInfo.requestedPermissions = permissions
whenever(
- packageManager.getPackageInfo(
+ packageManager.getPackageInfoAsUser(
anyString(),
- eq(PackageManager.GET_PERMISSIONS)
+ eq(PackageManager.GET_PERMISSIONS),
+ anyInt(),
)
).thenReturn(packageInfo)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
index fb62ba75e056..edf91fe62e7d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -234,14 +234,25 @@ class DesktopModeStatusTest : ShellTestCase() {
assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
}
+ @DisableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE)
@Test
fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsFalse() {
doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
- doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
+ doReturn(false).whenever(mockResources)
+ .getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
}
+ @EnableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE)
+ @Test
+ fun isPDDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ }
+
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index e5a6a6d258dd..70603fad37b9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
@@ -213,7 +214,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -239,7 +240,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
@@ -262,7 +263,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
@@ -524,7 +525,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(new TestRemoteTransition(), "Test"),
- mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index e9c4c31729e9..5dff21860ef4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -211,11 +211,19 @@ public class StageCoordinatorTests extends ShellTestCase {
when(mSplitLayout.getDividerLeash()).thenReturn(dividerLeash);
mRootTask = new TestRunningTaskInfoBuilder().build();
- SurfaceControl rootLeash = new SurfaceControl.Builder().setName("test").build();
+ SurfaceControl rootLeash = new SurfaceControl.Builder().setName("splitRoot").build();
mStageCoordinator.onTaskAppeared(mRootTask, rootLeash);
mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
+ SurfaceControl mainRootLeash = new SurfaceControl.Builder().setName("mainRoot").build();
+ SurfaceControl sideRootLeash = new SurfaceControl.Builder().setName("sideRoot").build();
+ mMainStage.mRootLeash = mainRootLeash;
+ mSideStage.mRootLeash = sideRootLeash;
+ SurfaceControl mainDimLayer = new SurfaceControl.Builder().setName("mainDim").build();
+ SurfaceControl sideDimLayer = new SurfaceControl.Builder().setName("sideDim").build();
+ mMainStage.mDimLayer = mainDimLayer;
+ mSideStage.mDimLayer = sideDimLayer;
doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
@@ -475,7 +483,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.startTask(mTaskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/,
null, SPLIT_INDEX_UNDEFINED);
verify(mSplitScreenTransitions).startEnterTransition(anyInt(),
- mWctCaptor.capture(), any(), any(), anyInt(), anyBoolean());
+ mWctCaptor.capture(), any(), any(), anyInt(), anyBoolean(), anyInt());
int windowingMode = mWctCaptor.getValue().getChanges().get(mBinder).getWindowingMode();
assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 3099b0f5cf66..a122c3820dcb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX;
import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
@@ -44,6 +45,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -196,6 +198,73 @@ public class HomeTransitionObserverTest extends ShellTestCase {
}
@Test
+ @DisableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
+ public void startDragToDesktopFinished_flagDisabled_doesNotTriggerCallback()
+ throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+ IBinder transition = mock(IBinder.class);
+ mHomeTransitionObserver.onTransitionReady(
+ transition,
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false);
+
+ verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean());
+ }
+
+ @Test
+ @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
+ public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+ IBinder transition = mock(IBinder.class);
+ mHomeTransitionObserver.onTransitionReady(
+ transition,
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true);
+
+ verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean());
+ }
+
+ @Test
+ @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
+ public void startDragToDesktopFinished_triggersCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+ IBinder transition = mock(IBinder.class);
+ mHomeTransitionObserver.onTransitionReady(
+ transition,
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false);
+
+ verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true);
+ }
+
+ @Test
@EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE})
public void testDragTaskToBubbleOverHome_notifiesHomeIsVisible() throws RemoteException {
ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt
new file mode 100644
index 000000000000..048981d634ef
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/RemoteTransitionHandlerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.WindowManager
+import android.window.RemoteTransition
+import android.window.TransitionFilter
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestSyncExecutor
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Test class for [RemoteTransitionHandler].
+ *
+ * atest WMShellUnitTests:RemoteTransitionHandlerTest
+ */
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class RemoteTransitionHandlerTest : ShellTestCase() {
+
+ private val testExecutor: TestSyncExecutor = TestSyncExecutor()
+
+ private val testRemoteTransition = RemoteTransition(TestRemoteTransition())
+ private lateinit var handler: RemoteTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler = RemoteTransitionHandler(testExecutor)
+ }
+
+ @Test
+ fun handleRequest_noRemoteTransition_returnsNull() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, null)
+
+ assertNull(handler.handleRequest(mock(), request))
+ }
+
+ @Test
+ fun handleRequest_testRemoteTransition_returnsWindowContainerTransaction() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, testRemoteTransition)
+
+ assertTrue(handler.handleRequest(mock(), request) is WindowContainerTransaction)
+ }
+
+ @Test
+ fun startAnimation_noRemoteTransition_returnsFalse() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, null)
+ handler.handleRequest(mock(), request)
+
+ val isHandled = handler.startAnimation(
+ /* transition= */ mock(),
+ /* info= */ createTransitionInfo(),
+ /* startTransaction= */ mock(),
+ /* finishTransaction= */ mock(),
+ /* finishCallback= */ {},
+ )
+
+ assertFalse(isHandled)
+ }
+
+ @Test
+ fun startAnimation_remoteTransition_returnsTrue() {
+ val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, testRemoteTransition)
+ handler.addFiltered(TransitionFilter(), testRemoteTransition)
+ handler.handleRequest(mock(), request)
+
+ val isHandled = handler.startAnimation(
+ /* transition= */ testRemoteTransition.remoteTransition.asBinder(),
+ /* info= */ createTransitionInfo(),
+ /* startTransaction= */ mock(),
+ /* finishTransaction= */ mock(),
+ /* finishCallback= */ {},
+ )
+
+ assertTrue(isHandled)
+ }
+
+ private fun createTransitionInfo(
+ type: Int = WindowManager.TRANSIT_OPEN,
+ changeMode: Int = WindowManager.TRANSIT_CLOSE,
+ ): TransitionInfo =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = changeMode
+ parent = null
+ }
+ )
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
index 257bbb5603a7..b07b6c1a3a87 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
@@ -22,6 +22,7 @@ import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.SurfaceControl
+import android.window.TaskSnapshot
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
@@ -33,6 +34,7 @@ import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.google.common.truth.Truth.assertThat
import org.junit.After
+import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -84,7 +86,29 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java)
}
- private fun createMenu(task: RunningTaskInfo) = DesktopHeaderManageWindowsMenu(
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun testShow_nullSnapshotDoesNotCauseNPE() {
+ val task = createFreeformTask()
+ val snapshotList = listOf(Pair(/* index = */ 1, /* snapshot = */ null))
+ // Set as immersive so that menu is created as system view container (simpler of the
+ // options)
+ userRepositories.getProfile(DEFAULT_USER_ID).setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = true
+ )
+ try {
+ menu = createMenu(task, snapshotList)
+ } catch (e: NullPointerException) {
+ fail("Null snapshot should not have thrown null pointer exception")
+ }
+ }
+
+ private fun createMenu(
+ task: RunningTaskInfo,
+ snapshotList: List<Pair<Int, TaskSnapshot?>> = emptyList()
+ ) = DesktopHeaderManageWindowsMenu(
callerTaskInfo = task,
x = 0,
y = 0,
@@ -94,7 +118,7 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
desktopUserRepositories = userRepositories,
surfaceControlBuilderSupplier = { SurfaceControl.Builder() },
surfaceControlTransactionSupplier = { SurfaceControl.Transaction() },
- snapshotList = emptyList(),
+ snapshotList = snapshotList,
onIconClickListener = {},
onOutsideClickListener = {},
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index 067dcec5d65d..b1f92411c5a3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -28,6 +28,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
@@ -109,7 +110,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
onTaskOpening(task, taskSurface)
assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
verify(decoration).close()
@@ -165,7 +166,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
setLargeScreen(false)
setUpMockDecorationForTask(task)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index e89a122595d5..ad3426e82805 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -51,6 +51,7 @@ import android.view.SurfaceView
import android.view.View
import android.view.ViewRootImpl
import android.view.WindowInsets.Type.statusBars
+import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp
import androidx.test.filters.SmallTest
@@ -115,7 +116,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.canInternalDisplayHostDesktops(Mockito.any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupportedOnDisplay(Mockito.any(),
+ Mockito.any()) }
doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(Mockito.any()) }
doReturn(false).`when` { DesktopModeStatus.overridesShowAppHandle(Mockito.any()) }
@@ -133,7 +135,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
verify(decoration).close()
@@ -148,12 +150,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
val taskSurface = SurfaceControl()
setUpMockDecorationForTask(task)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
task.setWindowingMode(WINDOWING_MODE_FREEFORM)
task.setActivityType(ACTIVITY_TYPE_STANDARD)
- onTaskChanging(task, taskSurface)
+ onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@@ -394,7 +396,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
- doReturn(true).`when` { DesktopModeStatus.canInternalDisplayHostDesktops(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupportedOnDisplay(any(), any()) }
setUpMockDecorationsForTasks(task)
onTaskOpening(task)
@@ -757,20 +759,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
}
@Test
- fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
- val toSplitScreenListenerCaptor = forClass(Function0::class.java)
- as ArgumentCaptor<Function0<Unit>>
- val decor = createOpenTaskDecoration(
- windowingMode = WINDOWING_MODE_MULTI_WINDOW,
- onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
- )
-
- toSplitScreenListenerCaptor.value.invoke()
-
- verify(decor).disposeStatusBarInputLayer()
- }
-
- @Test
fun testDecor_onClickToOpenBrowser_closeMenus() {
val openInBrowserListenerCaptor = forClass(Consumer::class.java)
as ArgumentCaptor<Consumer<Intent>>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 81dfaed56b6f..4c9c2f14d805 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -79,10 +79,12 @@ import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
+import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
import org.mockito.Mockito
@@ -124,6 +126,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockShellController = mock<ShellController>()
protected val testShellExecutor = TestShellExecutor()
protected val mockAppHeaderViewHolderFactory = mock<AppHeaderViewHolder.Factory>()
+ protected val mockAppHandleViewHolderFactory = mock<AppHandleViewHolder.Factory>()
protected val mockRootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
protected val mockShellCommandHandler = mock<ShellCommandHandler>()
protected val mockWindowManager = mock<IWindowManager>()
@@ -174,6 +177,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
protected lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
protected lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+ protected lateinit var appHandleAndHeaderVisibilityHelper: AppHandleAndHeaderVisibilityHelper
fun setUpCommon() {
spyContext = spy(mContext)
@@ -185,9 +189,13 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
+ whenever(display.type).thenReturn(Display.TYPE_INTERNAL)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
+ appHandleAndHeaderVisibilityHelper =
+ AppHandleAndHeaderVisibilityHelper(spyContext, mockDisplayController,
+ desktopModeCompatPolicy)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
testShellExecutor,
@@ -216,12 +224,14 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockInputMonitorFactory,
transactionFactory,
mockAppHeaderViewHolderFactory,
+ mockAppHandleViewHolderFactory,
mockRootTaskDisplayAreaOrganizer,
windowDecorByTaskIdSpy,
mockInteractionJankMonitor,
Optional.of(mockTasksLimiter),
mockAppHandleEducationController,
mockAppToWebEducationController,
+ appHandleAndHeaderVisibilityHelper,
mockCaptionHandleRepository,
Optional.of(mockActivityOrientationChangeHandler),
mockTaskPositionerFactory,
@@ -324,7 +334,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(),
any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(),
- any(), any(), any())
+ any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
@@ -346,12 +356,17 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
)
}
- protected fun onTaskChanging(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
+ protected fun onTaskChanging(
+ task: RunningTaskInfo,
+ leash: SurfaceControl = SurfaceControl(),
+ changeMode: Int
+ ) {
desktopModeWindowDecorViewModel.onTaskChanging(
task,
leash,
StubTransaction(),
- StubTransaction()
+ StubTransaction(),
+ changeMode
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index c4f70ac2297f..f37f2fb14bea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -60,7 +60,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
-import android.graphics.PointF;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.net.Uri;
@@ -115,6 +115,7 @@ import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Unit;
@@ -167,9 +168,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final boolean DEFAULT_IS_STATUSBAR_VISIBLE = true;
private static final boolean DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED = false;
private static final boolean DEFAULT_IS_IN_FULL_IMMERSIVE_MODE = false;
+ private static final boolean DEFAULT_IS_DRAGGING = false;
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
private static final boolean DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS = false;
+ private static final boolean DEFAULT_IS_RECENTS_TRANSITION_RUNNING = false;
+ private static final boolean DEFAULT_IS_MOVING_TO_BACK = false;
+
@Mock
private DisplayController mMockDisplayController;
@@ -190,8 +195,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private AppHeaderViewHolder.Factory mMockAppHeaderViewHolderFactory;
@Mock
+ private AppHandleViewHolder.Factory mMockAppHandleViewHolderFactory;
+ @Mock
private AppHeaderViewHolder mMockAppHeaderViewHolder;
@Mock
+ private AppHandleViewHolder mMockAppHandleViewHolder;
+ @Mock
private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
@Mock
private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier;
@@ -300,6 +309,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
when(mMockAppHeaderViewHolderFactory
.create(any(), any(), any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mMockAppHeaderViewHolder);
+ when(mMockAppHandleViewHolderFactory
+ .create(any(), any(), any(), any(), any()))
+ .thenReturn(mMockAppHandleViewHolder);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
@@ -415,11 +427,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
/* shouldIgnoreCornerRadius= */ true,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
}
@@ -616,11 +631,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- /* shouldExcludeCaptionFromAppBounds */ true);
+ /* shouldExcludeCaptionFromAppBounds */ true,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
// Force consuming flags are disabled.
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
@@ -650,11 +668,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
assertThat(
@@ -728,11 +749,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
/* inFullImmersiveMode */ true,
+ DEFAULT_IS_DRAGGING,
insetsState,
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -755,11 +779,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
/* inFullImmersiveMode */ true,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -781,11 +808,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isStatusBarVisible */ false,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -807,11 +837,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -832,11 +865,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isStatusBarVisible */ false,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -857,11 +893,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
/* isKeyguardVisibleAndOccluded */ true,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -883,11 +922,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isStatusBarVisible */ true,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
/* inFullImmersiveMode */ true,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -901,16 +943,48 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isStatusBarVisible */ false,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
/* inFullImmersiveMode */ true,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX)
+ public void updateRelayoutParams_header_fullyImmersive_captionVisDuringDrag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ /* isStatusBarVisible */ false,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ /* inFullImmersiveMode */ true,
+ /* isDragging */ true,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
+
+ assertThat(relayoutParams.mIsCaptionVisible).isTrue();
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void updateRelayoutParams_header_fullyImmersive_overKeyguard_captionNotVisible() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -927,11 +1001,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ true,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -962,6 +1039,65 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
assertThat(relayoutParams.mAsyncViewHost).isFalse();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX)
+ public void updateRelayoutParams_handle_movingToBack_captionNotVisible() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ /* isMovingToBack= */ true);
+
+ assertThat(relayoutParams.mIsCaptionVisible).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX)
+ public void updateRelayoutParams_handle_inRecentsTransition_captionNotVisible() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ /* isRecentsTransitionRunning= */ true,
+ DEFAULT_IS_MOVING_TO_BACK);
+
+ assertThat(relayoutParams.mIsCaptionVisible).isFalse();
+ }
+
@Test
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1588,11 +1724,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
}
private DesktopModeWindowDecoration createWindowDecoration(
@@ -1635,9 +1774,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo, mMockSurfaceControl, mMockHandler, mMainExecutor,
mMockMainCoroutineDispatcher, mMockBgCoroutineScope, mBgExecutor,
mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory,
- mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser,
- mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier,
- WindowContainerTransaction::new, SurfaceControl::new,
+ mMockAppHandleViewHolderFactory, mMockRootTaskDisplayAreaOrganizer,
+ mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
+ mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger,
@@ -1764,7 +1903,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@NonNull DisplayController displayController,
@NonNull ActivityManager.RunningTaskInfo taskInfo,
@NonNull Context decorWindowContext,
- @NonNull Function2<? super Integer,? super Integer,? extends PointF>
+ @NonNull Function2<? super Integer,? super Integer,? extends Point>
positionSupplier,
@NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) {
return mMaximizeMenu;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index f984f6db13fc..2e46f6312d03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -98,8 +98,6 @@ class HandleMenuTest : ShellTestCase() {
private lateinit var handleMenu: HandleMenu
- private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION
-
@Before
fun setUp() {
val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
@@ -126,7 +124,6 @@ class HandleMenuTest : ShellTestCase() {
addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
- addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION)
addOverride(
R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN)
}
@@ -141,7 +138,7 @@ class HandleMenuTest : ShellTestCase() {
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of display.
- val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN)
+ val expected = Point(DISPLAY_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@@ -165,7 +162,7 @@ class HandleMenuTest : ShellTestCase() {
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split left task.
val expected = Point(
- SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ SPLIT_LEFT_BOUNDS.centerX() - MENU_WIDTH / 2,
MENU_TOP_MARGIN
)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
@@ -180,7 +177,7 @@ class HandleMenuTest : ShellTestCase() {
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split right task.
val expected = Point(
- SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ SPLIT_RIGHT_BOUNDS.centerX() - MENU_WIDTH / 2,
MENU_TOP_MARGIN
)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
@@ -323,7 +320,6 @@ class HandleMenuTest : ShellTestCase() {
private const val MENU_HEIGHT = 400
private const val MENU_TOP_MARGIN = 10
private const val MENU_START_MARGIN = 20
- private const val MENU_PILL_ELEVATION = 2
private const val MENU_PILL_SPACING_MARGIN = 4
private const val HANDLE_WIDTH = 80
private const val APP_NAME = "Test App"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index a6b077037b86..0798613ed632 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -559,6 +559,17 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testClose() = runOnUiThread {
+ verify(mockDisplayController, times(1))
+ .addDisplayWindowListener(eq(taskPositioner))
+
+ taskPositioner.close()
+
+ verify(mockDisplayController, times(1))
+ .removeDisplayWindowListener(eq(taskPositioner))
+ }
+
+ @Test
fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
Assert.assertFalse(taskPositioner.isResizingOrAnimating)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
index fa3d3e4016e9..011c8f0ae17e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -52,7 +52,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@@ -216,7 +216,7 @@ class ResizeVeilTest : ShellTestCase() {
veil.hideVeil()
- verifyZeroInteractions(mockTransaction)
+ verifyNoMoreInteractions(mockTransaction)
}
@Test
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 a2927fa3527b..2e95a979220c 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
@@ -60,6 +60,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
+import android.os.LocaleList;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
@@ -97,6 +98,7 @@ import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.function.Supplier;
/**
@@ -475,6 +477,50 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Test
+ public void testReinflateViewsOnLocaleListChange() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .build();
+ taskInfo.configuration.setLocales(new LocaleList(Locale.FRANCE, Locale.US));
+ final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo));
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+ clearInvocations(windowDecor);
+
+ final ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .build();
+ taskInfo2.configuration.setLocales(new LocaleList(Locale.US, Locale.FRANCE));
+ windowDecor.relayout(taskInfo2, true /* hasGlobalFocus */, Region.obtain());
+ // WindowDecoration#releaseViews should be called since the locale list has changed.
+ verify(windowDecor, times(1)).releaseViews(any());
+ }
+
+ @Test
+ public void testViewNotReinflatedWhenLocaleListNotChanged() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .build();
+ taskInfo.configuration.setLocales(new LocaleList(Locale.FRANCE, Locale.US));
+ final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo));
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+ clearInvocations(windowDecor);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+ // WindowDecoration#releaseViews should not be called since nothing has changed.
+ verify(windowDecor, never()).releaseViews(any());
+ }
+
+ @Test
public void testLayoutResultCalculation_fullWidthCaption() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
@@ -780,6 +826,18 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Test
+ public void testClose_withTaskDragResizerSet_callResizerClose() {
+ final TestWindowDecoration windowDecor = createWindowDecoration(
+ new TestRunningTaskInfoBuilder().build());
+ final TaskDragResizer taskDragResizer = mock(TaskDragResizer.class);
+ windowDecor.setTaskDragResizer(taskDragResizer);
+
+ windowDecor.close();
+
+ verify(taskDragResizer).close();
+ }
+
+ @Test
public void testRelayout_captionFrameChanged_insetsReapplied() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index c61e0eb3b5af..714d06211044 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
+import android.os.LocaleList
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableContext
@@ -39,6 +40,7 @@ import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader.AppResources
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -52,7 +54,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
/**
@@ -116,12 +118,14 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
@Test
fun testGetName_cached_returnsFromCache() {
val task = createTaskInfo(context.userId)
+ task.configuration.setLocales(LocaleList(Locale.US))
loader.onWindowDecorCreated(task)
loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock())
+ loader.localeListOnCache[task.taskId] = LocaleList(Locale.US)
loader.getName(task)
- verifyZeroInteractions(
+ verifyNoMoreInteractions(
mockPackageManager,
mockIconProvider,
mockHeaderIconFactory,
@@ -130,6 +134,19 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
}
@Test
+ fun testGetName_cached_localesChanged_loadsResourceAndCaches() {
+ val task = createTaskInfo(context.userId)
+ loader.onWindowDecorCreated(task)
+ loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock())
+ loader.localeListOnCache[task.taskId] = LocaleList(Locale.US, Locale.FRANCE)
+ task.configuration.setLocales(LocaleList(Locale.FRANCE, Locale.US))
+ doReturn("App Name but in French").whenever(mockPackageManager).getApplicationLabel(any())
+
+ assertThat(loader.getName(task)).isEqualTo("App Name but in French")
+ assertThat(loader.taskToResourceCache[task.taskId]?.appName).isEqualTo("App Name but in French")
+ }
+
+ @Test
fun testGetHeaderIcon_notCached_loadsResourceAndCaches() {
val task = createTaskInfo(context.userId)
loader.onWindowDecorCreated(task)
@@ -148,7 +165,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
loader.getHeaderIcon(task)
- verifyZeroInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory)
+ verifyNoMoreInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory)
}
@Test
@@ -170,7 +187,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
loader.getVeilIcon(task)
- verifyZeroInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory)
+ verifyNoMoreInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 844205682d31..42eab14042f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -70,6 +70,13 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
whenever(context.display).thenReturn(display)
whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner)
whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS)
+ whenever(transactionSupplierMock.get()).thenReturn(transaction)
+ whenever(transaction.show(any())).thenReturn(transaction)
+ whenever(transaction.setAlpha(any(), any())).thenReturn(transaction)
+ whenever(transaction.hide(any())).thenReturn(transaction)
+ whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.remove(any())).thenReturn(transaction)
desktopTilingWindowManager =
DesktopTilingDividerWindowManager(
config,
@@ -88,12 +95,6 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
@Test
@UiThreadTest
fun testWindowManager_isInitialisedAndReleased() {
- whenever(transactionSupplierMock.get()).thenReturn(transaction)
- whenever(transaction.hide(any())).thenReturn(transaction)
- whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.remove(any())).thenReturn(transaction)
-
desktopTilingWindowManager.generateViewHost(surfaceControl)
// Ensure a surfaceControl transaction runs to show the divider.
@@ -102,18 +103,11 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
desktopTilingWindowManager.release()
verify(transaction, times(1)).hide(any())
verify(transaction, times(1)).remove(any())
- verify(transaction, times(1)).apply()
}
@Test
@UiThreadTest
fun testWindowManager_accountsForRoundedCornerDimensions() {
- whenever(transactionSupplierMock.get()).thenReturn(transaction)
- whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.show(any())).thenReturn(transaction)
-
desktopTilingWindowManager.generateViewHost(surfaceControl)
// Ensure a surfaceControl transaction runs to show the divider.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index 399a51e1ed08..e4424f3c57f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.tiling
import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
@@ -24,8 +25,10 @@ import android.testing.AndroidTestingRunner
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -40,6 +43,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createPinnedTask
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
@@ -114,6 +118,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
private val split_divider_width = 10
@Captor private lateinit var wctCaptor: ArgumentCaptor<WindowContainerTransaction>
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<(() -> Unit)>
@Before
fun setUp() {
@@ -134,7 +139,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
userRepositories,
desktopModeEventLogger,
focusTransitionObserver,
- mainExecutor
+ mainExecutor,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
whenever(userRepositories.current).thenReturn(desktopRepository)
@@ -158,7 +163,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
BOUNDS,
)
- verify(toggleResizeDesktopTaskTransitionHandler).startTransition(capture(wctCaptor), any())
+ verify(toggleResizeDesktopTaskTransitionHandler)
+ .startTransition(capture(wctCaptor), any(), any())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
val leftBounds = getLeftTaskBounds()
@@ -185,7 +191,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
BOUNDS,
)
- verify(toggleResizeDesktopTaskTransitionHandler).startTransition(capture(wctCaptor), any())
+ verify(toggleResizeDesktopTaskTransitionHandler)
+ .startTransition(capture(wctCaptor), any(), any())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
val leftBounds = getRightTaskBounds()
@@ -220,7 +227,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
)
verify(toggleResizeDesktopTaskTransitionHandler, times(1))
- .startTransition(capture(wctCaptor), any())
+ .startTransition(capture(wctCaptor), any(), any())
verify(returnToDragStartAnimator, times(1)).start(any(), any(), any(), any(), anyOrNull())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
@@ -308,9 +315,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
DesktopTasksController.SnapPosition.LEFT,
BOUNDS,
)
+ verify(toggleResizeDesktopTaskTransitionHandler, times(2))
+ .startTransition(capture(wctCaptor), any(), capture(callbackCaptor))
+ (callbackCaptor.value).invoke()
task1.isFocused = true
- assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true))
+ .isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
@@ -341,6 +352,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
)
task1.isFocused = true
task3.isFocused = true
+ verify(toggleResizeDesktopTaskTransitionHandler, times(2))
+ .startTransition(capture(wctCaptor), any(), capture(callbackCaptor))
+ (callbackCaptor.value).invoke()
assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, true)).isFalse()
assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, true)).isTrue()
@@ -372,9 +386,14 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
DesktopTasksController.SnapPosition.LEFT,
BOUNDS,
)
-
- assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true)).isFalse()
- assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue()
+ verify(toggleResizeDesktopTaskTransitionHandler, times(2))
+ .startTransition(capture(wctCaptor), any(), capture(callbackCaptor))
+ (callbackCaptor.value).invoke()
+
+ assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true))
+ .isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true))
+ .isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
@@ -482,27 +501,29 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
tilingDecoration.onDividerHandleDragStart(motionEvent)
// Log start event for task1 and task2, but the tasks are the same in
// this test, so we verify the same log twice.
- verify(desktopModeEventLogger, times(2)).logTaskResizingStarted(
- ResizeTrigger.TILING_DIVIDER,
- DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
- task1,
- BOUNDS.width() / 2,
- BOUNDS.height(),
- displayController,
- )
+ verify(desktopModeEventLogger, times(2))
+ .logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
+ task1,
+ BOUNDS.width() / 2,
+ BOUNDS.height(),
+ displayController,
+ )
tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
// Log end event for task1 and task2, but the tasks are the same in
// this test, so we verify the same log twice.
- verify(desktopModeEventLogger, times(2)).logTaskResizingEnded(
- ResizeTrigger.TILING_DIVIDER,
- DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
- task1,
- BOUNDS.width(),
- BOUNDS.height(),
- displayController,
- )
+ verify(desktopModeEventLogger, times(2))
+ .logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
+ task1,
+ BOUNDS.width(),
+ BOUNDS.height(),
+ displayController,
+ )
}
@Test
@@ -535,6 +556,37 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
@Test
+ fun taskTiled_shouldBeRemoved_whenEnteringPip() {
+ val task1 = createPipTask()
+ val stableBounds = STABLE_BOUNDS_MOCK
+ whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+ whenever(tiledTaskHelper.taskInfo).thenReturn(task1)
+ whenever(tiledTaskHelper.desktopModeWindowDecoration).thenReturn(desktopWindowDecoration)
+ tilingDecoration.onAppTiled(
+ task1,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.LEFT,
+ BOUNDS,
+ )
+ tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
+ val changeInfo = createPipChangeTransition(task1)
+ tilingDecoration.onTransitionReady(
+ transition = mock(),
+ info = changeInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ assertThat(tilingDecoration.leftTaskResizingHelper).isNull()
+ verify(tiledTaskHelper, times(1)).dispose()
+ }
+
+ @Test
fun taskNotTiled_shouldNotBeRemoved_whenNotTiled() {
val task1 = createVisibleTask()
val task2 = createVisibleTask()
@@ -635,6 +687,23 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true)
}
+ private fun createPipTask() =
+ createPinnedTask().also {
+ whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true)
+ }
+
+ private fun createPipChangeTransition(task: RunningTaskInfo?, type: Int = TRANSIT_PIP) =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_PIP
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+
companion object {
private val NON_STABLE_BOUNDS_MOCK = Rect(50, 55, 100, 100)
private val STABLE_BOUNDS_MOCK = Rect(0, 0, 100, 100)
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a18c5f5f92f6..8ecd6ba9b253 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6520,41 +6520,79 @@ base::expected<StringPiece16, NullOrIOError> StringPoolRef::string16() const {
}
bool ResTable::getResourceFlags(uint32_t resID, uint32_t* outFlags) const {
- if (mError != NO_ERROR) {
- return false;
- }
+ if (mError != NO_ERROR) {
+ return false;
+ }
- const ssize_t p = getResourcePackageIndex(resID);
- const int t = Res_GETTYPE(resID);
- const int e = Res_GETENTRY(resID);
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
- if (p < 0) {
- if (Res_GETPACKAGE(resID)+1 == 0) {
- ALOGW("No package identifier when getting flags for resource number 0x%08x", resID);
- } else {
- ALOGW("No known package when getting flags for resource number 0x%08x", resID);
- }
- return false;
- }
- if (t < 0) {
- ALOGW("No type identifier when getting flags for resource number 0x%08x", resID);
- return false;
+ if (p < 0) {
+ if (Res_GETPACKAGE(resID)+1 == 0) {
+ ALOGW("No package identifier when getting flags for resource number 0x%08x", resID);
+ } else {
+ ALOGW("No known package when getting flags for resource number 0x%08x", resID);
}
+ return false;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting flags for resource number 0x%08x", resID);
+ return false;
+ }
- const PackageGroup* const grp = mPackageGroups[p];
- if (grp == NULL) {
- ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID);
- return false;
- }
+ const PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID);
+ return false;
+ }
- Entry entry;
- status_t err = getEntry(grp, t, e, NULL, &entry);
- if (err != NO_ERROR) {
- return false;
+ Entry entry;
+ status_t err = getEntry(grp, t, e, NULL, &entry);
+ if (err != NO_ERROR) {
+ return false;
+ }
+
+ *outFlags = entry.specFlags;
+ return true;
+}
+
+bool ResTable::getResourceEntryFlags(uint32_t resID, uint32_t* outFlags) const {
+ if (mError != NO_ERROR) {
+ return false;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ if (Res_GETPACKAGE(resID)+1 == 0) {
+ ALOGW("No package identifier when getting flags for resource number 0x%08x", resID);
+ } else {
+ ALOGW("No known package when getting flags for resource number 0x%08x", resID);
}
+ return false;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting flags for resource number 0x%08x", resID);
+ return false;
+ }
- *outFlags = entry.specFlags;
- return true;
+ const PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID);
+ return false;
+ }
+
+ Entry entry;
+ status_t err = getEntry(grp, t, e, NULL, &entry);
+ if (err != NO_ERROR) {
+ return false;
+ }
+
+ *outFlags = entry.entry->flags();
+ return true;
}
bool ResTable::isPackageDynamic(uint8_t packageID) const {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 30594dcfa939..63b28da075cd 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1265,6 +1265,9 @@ struct ResTable_config
// Varies in length from 3 to 8 chars. Zero-filled value.
char localeNumberingSystem[8];
+ // Mark all padding explicitly so it's clear how much we can expand it.
+ char endPadding[3];
+
void copyFromDeviceNoSwap(const ResTable_config& o) {
const auto o_size = dtohl(o.size);
if (o_size >= sizeof(ResTable_config)) [[likely]] {
@@ -1422,6 +1425,13 @@ struct ResTable_config
void swapHtoD_slow();
};
+// Fix the struct size for backward compatibility
+static_assert(sizeof(ResTable_config) == 64);
+
+// Make sure there's no unaccounted padding in the structure.
+static_assert(offsetof(ResTable_config, endPadding) +
+ sizeof(ResTable_config::endPadding) == sizeof(ResTable_config));
+
/**
* A specification of the resources defined by a particular type.
*
@@ -1583,6 +1593,8 @@ union ResTable_entry
// If set, this is a compact entry with data type and value directly
// encoded in the this entry, see ResTable_entry::compact
FLAG_COMPACT = 0x0008,
+ // If set, this entry relies on read write android feature flags
+ FLAG_USES_FEATURE_FLAGS = 0x0010,
};
struct Full {
@@ -1612,6 +1624,7 @@ union ResTable_entry
uint16_t flags() const { return dtohs(full.flags); };
bool is_compact() const { return flags() & FLAG_COMPACT; }
bool is_complex() const { return flags() & FLAG_COMPLEX; }
+ bool uses_feature_flags() const { return flags() & FLAG_USES_FEATURE_FLAGS; }
size_t size() const {
return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
@@ -2029,6 +2042,8 @@ public:
bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const;
+ bool getResourceEntryFlags(uint32_t resID, uint32_t* outFlags) const;
+
/**
* Returns whether or not the package for the given resource has been dynamically assigned.
* If the resource can't be found, returns 'false'.
diff --git a/libs/hostgraphics/HostBufferQueue.cpp b/libs/hostgraphics/HostBufferQueue.cpp
index 7e14b88a47fa..ef5406250251 100644
--- a/libs/hostgraphics/HostBufferQueue.cpp
+++ b/libs/hostgraphics/HostBufferQueue.cpp
@@ -29,6 +29,7 @@ public:
}
virtual status_t detachBuffer(int slot) {
+ mBuffer.clear();
return OK;
}
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 56191c01aaef..87a43fcb0855 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -29,6 +29,7 @@ using namespace std;
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_RuntimeXfermode(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
@@ -131,6 +132,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
{"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
{"android.graphics.RenderEffect", REG_JNI(register_android_graphics_RenderEffect)},
+ {"android.graphics.RuntimeXfermode", REG_JNI(register_android_graphics_RuntimeXfermode)},
{"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
{"android.graphics.YuvImage", REG_JNI(register_android_graphics_YuvImage)},
{"android.graphics.animation.NativeInterpolatorFactory",
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index a210ddf54b2e..7d227f793817 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -722,10 +722,15 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() {
auto canvas = SkCanvas(recycledPixels->getSkBitmap());
SkRect destination = SkRect::Make(recycledPixels->info().bounds());
- destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds()));
- canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination,
- SkSamplingOptions(SkFilterMode::kLinear), nullptr,
- SkCanvas::kFast_SrcRectConstraint);
+ if (destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds()))) {
+ canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination,
+ SkSamplingOptions(SkFilterMode::kLinear), nullptr,
+ SkCanvas::kFast_SrcRectConstraint);
+ } else {
+ // The canvas would have discarded the draw operation automatically, but
+ // this case should have been detected before getting to this point.
+ ALOGE("Copy destination does not intersect image bounds");
+ }
} else {
void* dst = recycledPixels->pixels();
const size_t dstRowBytes = mRecycledBitmap->rowBytes();
diff --git a/location/java/android/location/GnssClock.java b/location/java/android/location/GnssClock.java
index 62f50b57520c..6930f365adc1 100644
--- a/location/java/android/location/GnssClock.java
+++ b/location/java/android/location/GnssClock.java
@@ -349,7 +349,7 @@ public final class GnssClock implements Parcelable {
* Gets the clock's Drift in nanoseconds per second.
*
* <p>This value is the instantaneous time-derivative of the value provided by
- * {@link #getBiasNanos()}.
+ * the sum of {@link #getFullBiasNanos()} and {@link #getBiasNanos()}.
*
* <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master
* clock) frequency. The error estimate for this reported drift is
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 83b1778fd611..496ba501e49c 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -179,3 +179,18 @@ flag {
}
}
+flag {
+ name: "gnss_location_provider_overlay_2025_devices"
+ namespace: "location"
+ description: "Flag for GNSS location provider overlay for 2025 devices"
+ bug: "398254728"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "gnss_assistance_interface_jni"
+ namespace: "location"
+ description: "Flag for GNSS assistance interface JNI"
+ bug: "209078566"
+}
+
diff --git a/media/java/Android.bp b/media/java/Android.bp
index 6878f9d61f6d..28b9d3bbc167 100644
--- a/media/java/Android.bp
+++ b/media/java/Android.bp
@@ -15,6 +15,7 @@ filegroup {
],
exclude_srcs: [
":framework-media-tv-tunerresourcemanager-sources-aidl",
+ ":framework-media-quality-sources-aidl",
],
visibility: [
"//frameworks/base",
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d082c7384fd1..32af7c6fca68 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4857,7 +4857,7 @@ public class AudioManager {
focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId);
}
- return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, focusReceiver);
+ return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, focusReceiver, afr);
}
/**
@@ -5070,7 +5070,7 @@ public class AudioManager {
focusReceiver = addClientIdToFocusReceiverLocked(clientId);
}
- return handleExternalAudioPolicyWaitIfNeeded(clientId, focusReceiver);
+ return handleExternalAudioPolicyWaitIfNeeded(clientId, focusReceiver, afr);
}
@GuardedBy("mFocusRequestsLock")
@@ -5086,11 +5086,20 @@ public class AudioManager {
}
private @FocusRequestResult int handleExternalAudioPolicyWaitIfNeeded(String clientId,
- BlockingFocusResultReceiver focusReceiver) {
+ BlockingFocusResultReceiver focusReceiver, @NonNull AudioFocusRequest afr) {
focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
- if (DEBUG && !focusReceiver.receivedResult()) {
- Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded"
- + " response from ext policy timed out, denying request");
+ if (!focusReceiver.receivedResult()) {
+ if (DEBUG) {
+ Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded"
+ + " response from ext policy timed out, denying request");
+ }
+ try {
+ // To prevent from orphan focus holder, cleanup
+ abandonAudioFocus(afr.getOnAudioFocusChangeListener());
+ } catch (Exception e) {
+ Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded failed to abandon audio"
+ +" focus after time out, error: " + e.getMessage());
+ }
}
synchronized (mFocusRequestsLock) {
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index 85bc8efe2750..e9590d50d719 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -18,6 +18,7 @@ package android.media;
import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
+import android.media.SuggestedDeviceInfo;
import android.os.Bundle;
import android.os.UserHandle;
@@ -37,4 +38,6 @@ oneway interface IMediaRouter2 {
*/
void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
in MediaRoute2Info route);
+ void notifyDeviceSuggestionsUpdated(String suggestingPackageName,
+ in List<SuggestedDeviceInfo> suggestions);
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 21908b2ca2e0..1c399d6958bb 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -21,6 +21,7 @@ import android.media.MediaRoute2Info;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.SuggestedDeviceInfo;
/**
* {@hide}
@@ -33,6 +34,8 @@ oneway interface IMediaRouter2Manager {
in RouteDiscoveryPreference discoveryPreference);
void notifyRouteListingPreferenceChange(String packageName,
in @nullable RouteListingPreference routeListingPreference);
+ void notifyDeviceSuggestionsUpdated(String packageName, String suggestingPackageName,
+ in @nullable List<SuggestedDeviceInfo> suggestedDeviceInfo);
void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifyRequestFailed(int requestId, int reason);
void invalidateInstance();
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 961962f6a010..60881f4bfc30 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -25,6 +25,7 @@ import android.media.MediaRouterClientState;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.SuggestedDeviceInfo;
import android.os.Bundle;
import android.os.UserHandle;
/**
@@ -72,6 +73,10 @@ interface IMediaRouterService {
in MediaRoute2Info route);
void setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume);
void releaseSessionWithRouter2(IMediaRouter2 router, String sessionId);
+ void setDeviceSuggestionsWithRouter2(IMediaRouter2 router,
+ in @nullable List<SuggestedDeviceInfo> suggestedDeviceInfo);
+ @nullable Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2(
+ IMediaRouter2 router);
// Methods for MediaRouter2Manager
List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager);
@@ -98,4 +103,8 @@ interface IMediaRouterService {
String sessionId, int volume);
void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
boolean showMediaOutputSwitcherWithProxyRouter(IMediaRouter2Manager manager);
+ void setDeviceSuggestionsWithManager(IMediaRouter2Manager manager,
+ in @nullable List<SuggestedDeviceInfo> suggestedDeviceInfo);
+ @nullable Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager(
+ IMediaRouter2Manager manager);
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 4e86eacea404..f3b21bfdaa3c 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -3836,6 +3836,151 @@ public final class MediaCodecInfo {
maxBlocks, maxBlocksPerSecond,
blockSize, blockSize,
1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (GetFlag(() -> android.media.codec.Flags.apvSupport())
+ && mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_APV)) {
+ maxBlocksPerSecond = 11880;
+ maxBps = 7000000;
+
+ // Sample rate, and Bit rate for APV Codec,
+ // corresponding to the definitions in
+ // "10.1.4. Levels and bands"
+ // found at https://www.ietf.org/archive/id/draft-lim-apv-03.html
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ long SR = 0; // luma sample rate
+ int BR = 0; // bit rate bps
+ switch (profileLevel.level) {
+ case CodecProfileLevel.APVLevel1Band0:
+ SR = 3041280; BR = 7000000; break;
+ case CodecProfileLevel.APVLevel1Band1:
+ SR = 3041280; BR = 11000000; break;
+ case CodecProfileLevel.APVLevel1Band2:
+ SR = 3041280; BR = 14000000; break;
+ case CodecProfileLevel.APVLevel1Band3:
+ SR = 3041280; BR = 21000000; break;
+ case CodecProfileLevel.APVLevel11Band0:
+ SR = 6082560; BR = 14000000; break;
+ case CodecProfileLevel.APVLevel11Band1:
+ SR = 6082560; BR = 21000000; break;
+ case CodecProfileLevel.APVLevel11Band2:
+ SR = 6082560; BR = 28000000; break;
+ case CodecProfileLevel.APVLevel11Band3:
+ SR = 6082560; BR = 42000000; break;
+ case CodecProfileLevel.APVLevel2Band0:
+ SR = 15667200; BR = 36000000; break;
+ case CodecProfileLevel.APVLevel2Band1:
+ SR = 15667200; BR = 53000000; break;
+ case CodecProfileLevel.APVLevel2Band2:
+ SR = 15667200; BR = 71000000; break;
+ case CodecProfileLevel.APVLevel2Band3:
+ SR = 15667200; BR = 106000000; break;
+ case CodecProfileLevel.APVLevel21Band0:
+ SR = 31334400; BR = 71000000; break;
+ case CodecProfileLevel.APVLevel21Band1:
+ SR = 31334400; BR = 106000000; break;
+ case CodecProfileLevel.APVLevel21Band2:
+ SR = 31334400; BR = 141000000; break;
+ case CodecProfileLevel.APVLevel21Band3:
+ SR = 31334400; BR = 212000000; break;
+ case CodecProfileLevel.APVLevel3Band0:
+ SR = 66846720; BR = 101000000; break;
+ case CodecProfileLevel.APVLevel3Band1:
+ SR = 66846720; BR = 151000000; break;
+ case CodecProfileLevel.APVLevel3Band2:
+ SR = 66846720; BR = 201000000; break;
+ case CodecProfileLevel.APVLevel3Band3:
+ SR = 66846720; BR = 301000000; break;
+ case CodecProfileLevel.APVLevel31Band0:
+ SR = 133693440; BR = 201000000; break;
+ case CodecProfileLevel.APVLevel31Band1:
+ SR = 133693440; BR = 301000000; break;
+ case CodecProfileLevel.APVLevel31Band2:
+ SR = 133693440; BR = 401000000; break;
+ case CodecProfileLevel.APVLevel31Band3:
+ SR = 133693440; BR = 602000000; break;
+ case CodecProfileLevel.APVLevel4Band0:
+ SR = 265420800; BR = 401000000; break;
+ case CodecProfileLevel.APVLevel4Band1:
+ SR = 265420800; BR = 602000000; break;
+ case CodecProfileLevel.APVLevel4Band2:
+ SR = 265420800; BR = 780000000; break;
+ case CodecProfileLevel.APVLevel4Band3:
+ SR = 265420800; BR = 1170000000; break;
+ case CodecProfileLevel.APVLevel41Band0:
+ SR = 530841600; BR = 780000000; break;
+ case CodecProfileLevel.APVLevel41Band1:
+ SR = 530841600; BR = 1170000000; break;
+ case CodecProfileLevel.APVLevel41Band2:
+ SR = 530841600; BR = 1560000000; break;
+ case CodecProfileLevel.APVLevel41Band3:
+ // Current API allows bitrates only up to Max Integer
+ // Hence we are limiting internal limits to Integer.MAX_VALUE
+ // even when actual Level/Band limits are higher
+ SR = 530841600; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel5Band0:
+ SR = 1061683200; BR = 1560000000; break;
+ case CodecProfileLevel.APVLevel5Band1:
+ SR = 1061683200; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel5Band2:
+ SR = 1061683200; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel5Band3:
+ SR = 1061683200; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel51Band0:
+ case CodecProfileLevel.APVLevel51Band1:
+ case CodecProfileLevel.APVLevel51Band2:
+ case CodecProfileLevel.APVLevel51Band3:
+ SR = 2123366400; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel6Band0:
+ case CodecProfileLevel.APVLevel6Band1:
+ case CodecProfileLevel.APVLevel6Band2:
+ case CodecProfileLevel.APVLevel6Band3:
+ SR = 4777574400L; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel61Band0:
+ case CodecProfileLevel.APVLevel61Band1:
+ case CodecProfileLevel.APVLevel61Band2:
+ case CodecProfileLevel.APVLevel61Band3:
+ SR = 8493465600L; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel7Band0:
+ case CodecProfileLevel.APVLevel7Band1:
+ case CodecProfileLevel.APVLevel7Band2:
+ case CodecProfileLevel.APVLevel7Band3:
+ SR = 16986931200L; BR = Integer.MAX_VALUE; break;
+ case CodecProfileLevel.APVLevel71Band0:
+ case CodecProfileLevel.APVLevel71Band1:
+ case CodecProfileLevel.APVLevel71Band2:
+ case CodecProfileLevel.APVLevel71Band3:
+ SR = 33973862400L; BR = Integer.MAX_VALUE; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.APVProfile422_10:
+ case CodecProfileLevel.APVProfile422_10HDR10:
+ case CodecProfileLevel.APVProfile422_10HDR10Plus:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+ maxBps = Math.max(BR, maxBps);
+ }
+
+ final int blockSize = 16;
+ maxBlocks = Integer.MAX_VALUE;
+ maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+ maxBlocks = (int) Math.min((long) maxBlocks, maxBlocksPerSecond);
+ // Max frame size in APV is 2^24
+ int maxLengthInBlocks = Utils.divUp((int) Math.pow(2, 24), blockSize);
+ maxLengthInBlocks = Math.min(maxLengthInBlocks, maxBlocks);
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ blockSize, blockSize,
+ 2 /* widthAlignment */, 1 /* heightAlignment */);
} else {
Log.w(TAG, "Unsupported mime " + mime);
// using minimal bitrate here. should be overriden by
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 4ae8daa63e1d..6a33b374b21c 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -759,12 +759,29 @@ public abstract class MediaRoute2ProviderService extends Service {
/**
* Updates routes of the provider and notifies the system media router service.
+ *
+ * @throws IllegalArgumentException If {@code routes} contains a route that {@link
+ * MediaRoute2Info#getSupportedRoutingTypes() supports} both system media routing and remote
+ * routing but doesn't contain any {@link MediaRoute2Info#getDeduplicationIds()
+ * deduplication ids}.
*/
public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
requireNonNull(routes, "routes must not be null");
List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size());
for (MediaRoute2Info route : routes) {
+ if (Flags.enableMirroringInMediaRouter2()
+ && route.supportsRemoteRouting()
+ && route.supportsSystemMediaRouting()
+ && route.getDeduplicationIds().isEmpty()) {
+ String errorMessage =
+ TextUtils.formatSimple(
+ "Route with id='%s' name='%s' supports both system media and remote"
+ + " type routing, but doesn't contain a deduplication id, which"
+ + " it needs. You can add the route id as a deduplication id.",
+ route.getOriginalId(), route.getName());
+ throw new IllegalArgumentException(errorMessage);
+ }
if (route.isSystemRouteType()) {
Log.w(
TAG,
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index b57476f4341f..6e0821f9f89b 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -183,7 +183,17 @@ public class MediaRouter {
appContext.registerReceiver(new VolumeChangeReceiver(),
new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
- mDisplayService.registerDisplayListener(this, mHandler);
+ if (com.android.server.display.feature.flags.Flags
+ .displayListenerPerformanceImprovements()
+ && com.android.server.display.feature.flags.Flags
+ .delayImplicitRrRegistrationUntilRrAccessed()) {
+ mDisplayService.registerDisplayListener(this, mHandler,
+ DisplayManager.EVENT_TYPE_DISPLAY_ADDED
+ | DisplayManager.EVENT_TYPE_DISPLAY_CHANGED
+ | DisplayManager.EVENT_TYPE_DISPLAY_REMOVED);
+ } else {
+ mDisplayService.registerDisplayListener(this, mHandler);
+ }
AudioRoutesInfo newAudioRoutes = null;
try {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3af36a404c30..db305effac3f 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -22,6 +22,7 @@ import static com.android.media.flags.Flags.FLAG_ENABLE_GET_TRANSFERABLE_ROUTES;
import static com.android.media.flags.Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL;
import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
import static com.android.media.flags.Flags.FLAG_ENABLE_SCREEN_OFF_SCANNING;
+import static com.android.media.flags.Flags.FLAG_ENABLE_SUGGESTED_DEVICE_API;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -159,6 +160,8 @@ public final class MediaRouter2 {
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords =
new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<DeviceSuggestionsCallbackRecord>
+ mDeviceSuggestionsCallbackRecords = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
new CopyOnWriteArrayList<>();
@@ -198,6 +201,10 @@ public final class MediaRouter2 {
@Nullable
private RouteListingPreference mRouteListingPreference;
+ @GuardedBy("mLock")
+ @Nullable
+ private Map<String, List<SuggestedDeviceInfo>> mSuggestedDeviceInfo = new HashMap<>();
+
/**
* Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback
* dispatch. This is only used to determine what callback a route should be assigned to (added,
@@ -760,6 +767,27 @@ public final class MediaRouter2 {
}
/**
+ * Registers the given callback to be invoked when the {@link SuggestedDeviceInfo} of the target
+ * router changes.
+ *
+ * <p>Calls using a previously registered callback will overwrite the previous executor.
+ *
+ * @hide
+ */
+ public void registerDeviceSuggestionsCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull DeviceSuggestionsCallback deviceSuggestionsCallback) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(deviceSuggestionsCallback, "callback must not be null");
+
+ DeviceSuggestionsCallbackRecord record =
+ new DeviceSuggestionsCallbackRecord(executor, deviceSuggestionsCallback);
+
+ mDeviceSuggestionsCallbackRecords.remove(record);
+ mDeviceSuggestionsCallbackRecords.add(record);
+ }
+
+ /**
* Unregisters the given callback to not receive {@link RouteListingPreference} change events.
*
* @see #registerRouteListingPreferenceUpdatedCallback(Executor, Consumer)
@@ -779,6 +807,21 @@ public final class MediaRouter2 {
}
/**
+ * Unregisters the given callback to not receive {@link SuggestedDeviceInfo} change events.
+ *
+ * @see #registerDeviceSuggestionsCallback(Executor, DeviceSuggestionsCallback)
+ * @hide
+ */
+ public void unregisterDeviceSuggestionsCallback(@NonNull DeviceSuggestionsCallback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ if (!mDeviceSuggestionsCallbackRecords.remove(
+ new DeviceSuggestionsCallbackRecord(/* executor */ null, callback))) {
+ Log.w(TAG, "unregisterDeviceSuggestionsCallback: Ignoring an unknown" + " callback");
+ }
+ }
+
+ /**
* Shows the system output switcher dialog.
*
* <p>Should only be called when the context of MediaRouter2 is in the foreground and visible on
@@ -832,6 +875,36 @@ public final class MediaRouter2 {
}
/**
+ * Sets the suggested devices.
+ *
+ * <p>Use this method to inform the system UI that this device is suggested in the Output
+ * Switcher and media controls.
+ *
+ * <p>You should pass null to this method to clear a previously set suggestion without setting a
+ * new one.
+ *
+ * @param suggestedDeviceInfo The {@link SuggestedDeviceInfo} the router suggests should be
+ * provided to the user.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_SUGGESTED_DEVICE_API)
+ public void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ mImpl.setDeviceSuggestions(suggestedDeviceInfo);
+ }
+
+ /**
+ * Gets the current suggested devices.
+ *
+ * @return the suggested devices, keyed by the package name providing each suggestion list.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_SUGGESTED_DEVICE_API)
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions() {
+ return mImpl.getDeviceSuggestions();
+ }
+
+ /**
* Returns the current {@link RouteListingPreference} of the target router.
*
* <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns
@@ -1518,6 +1591,17 @@ public final class MediaRouter2 {
}
}
+ private void notifyDeviceSuggestionsUpdated(
+ @NonNull String suggestingPackageName,
+ @Nullable List<SuggestedDeviceInfo> deviceSuggestions) {
+ for (DeviceSuggestionsCallbackRecord record : mDeviceSuggestionsCallbackRecords) {
+ record.mExecutor.execute(
+ () ->
+ record.mDeviceSuggestionsCallback.onSuggestionUpdated(
+ suggestingPackageName, deviceSuggestions));
+ }
+ }
+
private void notifyTransfer(RoutingController oldController, RoutingController newController) {
for (TransferCallbackRecord record : mTransferCallbackRecords) {
record.mExecutor.execute(
@@ -1568,6 +1652,25 @@ public final class MediaRouter2 {
.build();
}
+ /**
+ * Callback for receiving events about device suggestions
+ *
+ * @hide
+ */
+ public interface DeviceSuggestionsCallback {
+
+ /**
+ * Called when suggestions are updated. Whenever you register a callback, this will be
+ * invoked with the current suggestions.
+ *
+ * @param suggestingPackageName the package that provided the suggestions.
+ * @param suggestedDeviceInfo the suggestions provided by the package.
+ */
+ void onSuggestionUpdated(
+ @NonNull String suggestingPackageName,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo);
+ }
+
/** Callback for receiving events about media route discovery. */
public abstract static class RouteCallback {
/**
@@ -2326,6 +2429,35 @@ public final class MediaRouter2 {
}
}
+ private static final class DeviceSuggestionsCallbackRecord {
+ public final Executor mExecutor;
+ public final DeviceSuggestionsCallback mDeviceSuggestionsCallback;
+
+ /* package */ DeviceSuggestionsCallbackRecord(
+ @NonNull Executor executor,
+ @NonNull DeviceSuggestionsCallback deviceSuggestionsCallback) {
+ mExecutor = executor;
+ mDeviceSuggestionsCallback = deviceSuggestionsCallback;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DeviceSuggestionsCallbackRecord)) {
+ return false;
+ }
+ return mDeviceSuggestionsCallback
+ == ((DeviceSuggestionsCallbackRecord) obj).mDeviceSuggestionsCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mDeviceSuggestionsCallback.hashCode();
+ }
+ }
+
static final class TransferCallbackRecord {
public final Executor mExecutor;
public final TransferCallback mTransferCallback;
@@ -2446,6 +2578,17 @@ public final class MediaRouter2 {
}
@Override
+ public void notifyDeviceSuggestionsUpdated(
+ String suggestingPackageName, List<SuggestedDeviceInfo> suggestions) {
+ mHandler.sendMessage(
+ obtainMessage(
+ MediaRouter2::notifyDeviceSuggestionsUpdated,
+ MediaRouter2.this,
+ suggestingPackageName,
+ suggestions));
+ }
+
+ @Override
public void requestCreateSessionByManager(
long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
mHandler.sendMessage(
@@ -2487,6 +2630,11 @@ public final class MediaRouter2 {
void setRouteListingPreference(@Nullable RouteListingPreference preference);
+ void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo);
+
+ @Nullable
+ Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions();
+
boolean showSystemOutputSwitcher();
List<MediaRoute2Info> getAllRoutes();
@@ -2687,6 +2835,29 @@ public final class MediaRouter2 {
}
@Override
+ public void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ synchronized (mLock) {
+ try {
+ mMediaRouterService.setDeviceSuggestionsWithManager(
+ mClient, suggestedDeviceInfo);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions() {
+ synchronized (mLock) {
+ try {
+ return mMediaRouterService.getDeviceSuggestionsWithManager(mClient);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
public boolean showSystemOutputSwitcher() {
try {
return mMediaRouterService.showMediaOutputSwitcherWithProxyRouter(mClient);
@@ -3296,6 +3467,23 @@ public final class MediaRouter2 {
notifyRouteListingPreferenceUpdated(routeListingPreference);
}
+ private void onDeviceSuggestionsChangeHandler(
+ @NonNull String packageName,
+ @NonNull String suggestingPackageName,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ if (!TextUtils.equals(getClientPackageName(), packageName)) {
+ return;
+ }
+ synchronized (mLock) {
+ if (Objects.equals(
+ mSuggestedDeviceInfo.get(suggestingPackageName), suggestedDeviceInfo)) {
+ return;
+ }
+ mSuggestedDeviceInfo.put(suggestingPackageName, suggestedDeviceInfo);
+ }
+ notifyDeviceSuggestionsUpdated(suggestingPackageName, suggestedDeviceInfo);
+ }
+
private void onRequestFailedOnHandler(int requestId, int reason) {
MediaRouter2Manager.TransferRequest matchingRequest = null;
for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
@@ -3390,6 +3578,20 @@ public final class MediaRouter2 {
}
@Override
+ public void notifyDeviceSuggestionsUpdated(
+ String packageName,
+ String suggestingPackageName,
+ @Nullable List<SuggestedDeviceInfo> deviceSuggestions) {
+ mHandler.sendMessage(
+ obtainMessage(
+ ProxyMediaRouter2Impl::onDeviceSuggestionsChangeHandler,
+ ProxyMediaRouter2Impl.this,
+ packageName,
+ suggestingPackageName,
+ deviceSuggestions));
+ }
+
+ @Override
public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
obtainMessage(
@@ -3553,6 +3755,30 @@ public final class MediaRouter2 {
}
@Override
+ public void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> deviceSuggestions) {
+ synchronized (mLock) {
+ try {
+ registerRouterStubIfNeededLocked();
+ mMediaRouterService.setDeviceSuggestionsWithRouter2(mStub, deviceSuggestions);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions() {
+ synchronized (mLock) {
+ try {
+ return mMediaRouterService.getDeviceSuggestionsWithRouter2(mStub);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
public boolean showSystemOutputSwitcher() {
synchronized (mLock) {
try {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 3f18eef2f9aa..bf88709eec33 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -1138,6 +1138,14 @@ public final class MediaRouter2Manager {
}
@Override
+ public void notifyDeviceSuggestionsUpdated(
+ String packageName,
+ String suggestingPackageName,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ // MediaRouter2Manager doesn't support device suggestions
+ }
+
+ @Override
public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
obtainMessage(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt b/media/java/android/media/SuggestedDeviceInfo.aidl
index 9b7cd704aa2f..eab642572ed2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
+++ b/media/java/android/media/SuggestedDeviceInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
-import javax.inject.Qualifier
+package android.media;
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Tracing
+parcelable SuggestedDeviceInfo;
diff --git a/media/java/android/media/SuggestedDeviceInfo.java b/media/java/android/media/SuggestedDeviceInfo.java
new file mode 100644
index 000000000000..2aa139fcca17
--- /dev/null
+++ b/media/java/android/media/SuggestedDeviceInfo.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.media;
+
+import static com.android.media.flags.Flags.FLAG_ENABLE_SUGGESTED_DEVICE_API;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Allows applications to suggest routes to the system UI (for example, in the System UI Output
+ * Switcher).
+ *
+ * @see MediaRouter2#setSuggestedDevice
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_SUGGESTED_DEVICE_API)
+public final class SuggestedDeviceInfo implements Parcelable {
+ @NonNull
+ public static final Creator<SuggestedDeviceInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ public SuggestedDeviceInfo createFromParcel(Parcel in) {
+ return new SuggestedDeviceInfo(in);
+ }
+
+ @Override
+ public SuggestedDeviceInfo[] newArray(int size) {
+ return new SuggestedDeviceInfo[size];
+ }
+ };
+
+ @NonNull private final String mDeviceDisplayName;
+
+ @NonNull private final String mRouteId;
+
+ private final int mType;
+
+ @NonNull private final Bundle mExtras;
+
+ private SuggestedDeviceInfo(Builder builder) {
+ mDeviceDisplayName = builder.mDeviceDisplayName;
+ mRouteId = builder.mRouteId;
+ mType = builder.mType;
+ mExtras = builder.mExtras;
+ }
+
+ private SuggestedDeviceInfo(Parcel in) {
+ mDeviceDisplayName = in.readString();
+ mRouteId = in.readString();
+ mType = in.readInt();
+ mExtras = in.readBundle();
+ }
+
+ /**
+ * Returns the name to be displayed to the user.
+ *
+ * @return The device display name.
+ */
+ @NonNull
+ public String getDeviceDisplayName() {
+ return mDeviceDisplayName;
+ }
+
+ /**
+ * Returns the route ID associated with the suggestion.
+ *
+ * @return The route ID.
+ */
+ @NonNull
+ public String getRouteId() {
+ return mRouteId;
+ }
+
+ /**
+ * Returns the device type associated with the suggestion.
+ *
+ * @return The device type.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the extras associated with the suggestion.
+ *
+ * @return The extras.
+ */
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ // SuggestedDeviceInfo Parcelable implementation.
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mDeviceDisplayName);
+ dest.writeString(mRouteId);
+ dest.writeInt(mType);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SuggestedDeviceInfo)) {
+ return false;
+ }
+ return Objects.equals(mDeviceDisplayName, ((SuggestedDeviceInfo) obj).mDeviceDisplayName)
+ && Objects.equals(mRouteId, ((SuggestedDeviceInfo) obj).mRouteId)
+ && mType == ((SuggestedDeviceInfo) obj).mType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDeviceDisplayName, mRouteId, mType);
+ }
+
+ @Override
+ public String toString() {
+ return mDeviceDisplayName + " | " + mRouteId + " | " + mType;
+ }
+
+ /** Builder for {@link SuggestedDeviceInfo}. */
+ public static final class Builder {
+ @NonNull private String mDeviceDisplayName;
+
+ @NonNull private String mRouteId;
+
+ @NonNull private Integer mType;
+
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Creates a new SuggestedDeviceInfo. The device display name, route ID, and type must be
+ * set. The extras cannot be null, but default to an empty {@link Bundle}.
+ *
+ * @throws IllegalArgumentException if the builder has a mandatory unset field.
+ */
+ public SuggestedDeviceInfo build() {
+ if (mDeviceDisplayName == null) {
+ throw new IllegalArgumentException("Device display name cannot be null");
+ }
+
+ if (mRouteId == null) {
+ throw new IllegalArgumentException("Route ID cannot be null.");
+ }
+
+ if (mType == null) {
+ throw new IllegalArgumentException("Device type cannot be null.");
+ }
+
+ if (mExtras == null) {
+ throw new IllegalArgumentException("Extras cannot be null.");
+ }
+
+ return new SuggestedDeviceInfo(this);
+ }
+
+ /**
+ * Sets the {@link #getDeviceDisplayName() device display name}.
+ *
+ * @throws IllegalArgumentException if the name is null or empty.
+ */
+ public Builder setDeviceDisplayName(@NonNull String deviceDisplayName) {
+ if (TextUtils.isEmpty(deviceDisplayName)) {
+ throw new IllegalArgumentException("Device display name cannot be null");
+ }
+ mDeviceDisplayName = deviceDisplayName;
+ return this;
+ }
+
+ /**
+ * Sets the {@link #getRouteId() route id}.
+ *
+ * @throws IllegalArgumentException if the route id is null or empty.
+ */
+ public Builder setRouteId(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("Device display name cannot be null");
+ }
+ mRouteId = routeId;
+ return this;
+ }
+
+ /** Sets the {@link #getType() device type}. */
+ public Builder setType(int type) {
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Sets the {@link #getExtras() extras}.
+ *
+ * <p>The default value is an empty {@link Bundle}.
+ *
+ * <p>Do not mutate the given {@link Bundle} after passing it to this method. You can use
+ * {@link Bundle#deepCopy()} to keep a mutable copy.
+ *
+ * @throws NullPointerException if the extras are null.
+ */
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = Objects.requireNonNull(extras, "extras must not be null");
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 0deed3982d9b..48e2f4e15238 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -62,6 +62,14 @@ flag {
}
flag {
+ name: "enable_suggested_device_api"
+ is_exported: true
+ namespace: "media_better_together"
+ description: "Enables the API allowing proxy routers to suggest routes."
+ bug: "393216553"
+}
+
+flag {
name: "enable_full_scan_with_media_content_control"
namespace: "media_better_together"
description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
@@ -234,3 +242,13 @@ flag {
description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
bug: "293743975"
}
+
+flag {
+ name: "fix_output_media_item_list_index_out_of_bounds_exception"
+ namespace: "media_better_together"
+ description: "Fixes a bug of causing IndexOutOfBoundsException when building media item list."
+ bug: "398246089"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/media/java/android/media/quality/Android.bp b/media/java/android/media/quality/Android.bp
new file mode 100644
index 000000000000..f620144e2880
--- /dev/null
+++ b/media/java/android/media/quality/Android.bp
@@ -0,0 +1,64 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "framework-media-quality-sources-aidl",
+ srcs: [
+ "aidl/android/media/quality/*.aidl",
+ ],
+ path: "aidl",
+}
+
+cc_library_headers {
+ name: "media_quality_headers",
+ export_include_dirs: ["include"],
+}
+
+cc_library_shared {
+ name: "libmedia_quality_include",
+
+ export_include_dirs: ["include"],
+ cflags: [
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+
+ srcs: [
+ ":framework-media-quality-sources-aidl",
+ ],
+}
+
+aidl_interface {
+ name: "media_quality_aidl_interface",
+ unstable: true,
+ local_include_dir: "aidl",
+ backend: {
+ java: {
+ enabled: true,
+ },
+ cpp: {
+ additional_shared_libraries: ["libmedia_quality_include"],
+ enabled: true,
+ },
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: false,
+ },
+ },
+ srcs: [
+ ":framework-media-quality-sources-aidl",
+ ],
+}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index e4de3e4420fe..fccdba8e727f 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -82,7 +82,7 @@ public class MediaQualityContract {
String PARAMETER_NAME = "_name";
String PARAMETER_PACKAGE = "_package";
String PARAMETER_INPUT_ID = "_input_id";
-
+ String VENDOR_PARAMETERS = "_vendor_parameters";
}
/**
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 0d6d32a22dae..bfd01380a2ee 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -274,9 +274,9 @@ public final class MediaQualityManager {
@NonNull String name,
@Nullable ProfileQueryParams options) {
try {
- Bundle optionsBundle = options == null
- ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle();
- return mService.getPictureProfile(type, name, optionsBundle, mUserHandle);
+ boolean includeParams = options == null || options.mParametersIncluded;
+ return mService.getPictureProfile(
+ type, name, includeParams, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -299,10 +299,9 @@ public final class MediaQualityManager {
public List<PictureProfile> getPictureProfilesByPackage(
@NonNull String packageName, @Nullable ProfileQueryParams options) {
try {
- Bundle optionsBundle = options == null
- ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle();
+ boolean includeParams = options == null || options.mParametersIncluded;
return mService.getPictureProfilesByPackage(
- packageName, optionsBundle, mUserHandle);
+ packageName, includeParams, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -321,9 +320,8 @@ public final class MediaQualityManager {
@NonNull
public List<PictureProfile> getAvailablePictureProfiles(@Nullable ProfileQueryParams options) {
try {
- Bundle optionsBundle = options == null
- ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle();
- return mService.getAvailablePictureProfiles(optionsBundle, mUserHandle);
+ boolean includeParams = options == null || options.mParametersIncluded;
+ return mService.getAvailablePictureProfiles(includeParams, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -344,7 +342,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public boolean setDefaultPictureProfile(@Nullable String pictureProfileId) {
try {
- return mService.setDefaultPictureProfile(pictureProfileId, mUserHandle);
+ return mService.setDefaultPictureProfile(pictureProfileId, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -361,7 +359,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public List<String> getPictureProfilePackageNames() {
try {
- return mService.getPictureProfilePackageNames(mUserHandle);
+ return mService.getPictureProfilePackageNames(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -373,7 +371,7 @@ public final class MediaQualityManager {
*/
public List<PictureProfileHandle> getPictureProfileHandle(String[] id) {
try {
- return mService.getPictureProfileHandle(id, mUserHandle);
+ return mService.getPictureProfileHandle(id, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -385,7 +383,7 @@ public final class MediaQualityManager {
*/
public List<SoundProfileHandle> getSoundProfileHandle(String[] id) {
try {
- return mService.getSoundProfileHandle(id, mUserHandle);
+ return mService.getSoundProfileHandle(id, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -401,7 +399,7 @@ public final class MediaQualityManager {
*/
public void createPictureProfile(@NonNull PictureProfile pp) {
try {
- mService.createPictureProfile(pp, mUserHandle);
+ mService.createPictureProfile(pp, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -416,7 +414,7 @@ public final class MediaQualityManager {
*/
public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
try {
- mService.updatePictureProfile(profileId, pp, mUserHandle);
+ mService.updatePictureProfile(profileId, pp, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -430,7 +428,7 @@ public final class MediaQualityManager {
*/
public void removePictureProfile(@NonNull String profileId) {
try {
- mService.removePictureProfile(profileId, mUserHandle);
+ mService.removePictureProfile(profileId, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -484,9 +482,8 @@ public final class MediaQualityManager {
@NonNull String name,
@Nullable ProfileQueryParams options) {
try {
- Bundle optionsBundle = options == null
- ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle();
- return mService.getSoundProfile(type, name, optionsBundle, mUserHandle);
+ boolean includeParams = options == null || options.mParametersIncluded;
+ return mService.getSoundProfile(type, name, includeParams, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -510,9 +507,9 @@ public final class MediaQualityManager {
public List<SoundProfile> getSoundProfilesByPackage(
@NonNull String packageName, @Nullable ProfileQueryParams options) {
try {
- Bundle optionsBundle = options == null
- ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle();
- return mService.getSoundProfilesByPackage(packageName, optionsBundle, mUserHandle);
+ boolean includeParams = options == null || options.mParametersIncluded;
+ return mService.getSoundProfilesByPackage(
+ packageName, includeParams, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -531,9 +528,8 @@ public final class MediaQualityManager {
@NonNull
public List<SoundProfile> getAvailableSoundProfiles(@Nullable ProfileQueryParams options) {
try {
- Bundle optionsBundle = options == null
- ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle();
- return mService.getAvailableSoundProfiles(optionsBundle, mUserHandle);
+ boolean includeParams = options == null || options.mParametersIncluded;
+ return mService.getAvailableSoundProfiles(includeParams, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -554,7 +550,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public boolean setDefaultSoundProfile(@Nullable String soundProfileId) {
try {
- return mService.setDefaultSoundProfile(soundProfileId, mUserHandle);
+ return mService.setDefaultSoundProfile(soundProfileId, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -572,7 +568,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public List<String> getSoundProfilePackageNames() {
try {
- return mService.getSoundProfilePackageNames(mUserHandle);
+ return mService.getSoundProfilePackageNames(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -589,7 +585,7 @@ public final class MediaQualityManager {
*/
public void createSoundProfile(@NonNull SoundProfile sp) {
try {
- mService.createSoundProfile(sp, mUserHandle);
+ mService.createSoundProfile(sp, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -604,7 +600,7 @@ public final class MediaQualityManager {
*/
public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) {
try {
- mService.updateSoundProfile(profileId, sp, mUserHandle);
+ mService.updateSoundProfile(profileId, sp, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -618,7 +614,7 @@ public final class MediaQualityManager {
*/
public void removeSoundProfile(@NonNull String profileId) {
try {
- mService.removeSoundProfile(profileId, mUserHandle);
+ mService.removeSoundProfile(profileId, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -636,7 +632,7 @@ public final class MediaQualityManager {
@NonNull
public List<ParameterCapability> getParameterCapabilities(@NonNull List<String> names) {
try {
- return mService.getParameterCapabilities(names, mUserHandle);
+ return mService.getParameterCapabilities(names, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -654,7 +650,7 @@ public final class MediaQualityManager {
@NonNull
public List<String> getPictureProfileAllowList() {
try {
- return mService.getPictureProfileAllowList(mUserHandle);
+ return mService.getPictureProfileAllowList(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -668,7 +664,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public void setPictureProfileAllowList(@NonNull List<String> packageNames) {
try {
- mService.setPictureProfileAllowList(packageNames, mUserHandle);
+ mService.setPictureProfileAllowList(packageNames, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -686,7 +682,7 @@ public final class MediaQualityManager {
@NonNull
public List<String> getSoundProfileAllowList() {
try {
- return mService.getSoundProfileAllowList(mUserHandle);
+ return mService.getSoundProfileAllowList(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -700,7 +696,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public void setSoundProfileAllowList(@NonNull List<String> packageNames) {
try {
- mService.setSoundProfileAllowList(packageNames, mUserHandle);
+ mService.setSoundProfileAllowList(packageNames, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -712,7 +708,7 @@ public final class MediaQualityManager {
*/
public boolean isSupported() {
try {
- return mService.isSupported(mUserHandle);
+ return mService.isSupported(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -730,7 +726,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public void setAutoPictureQualityEnabled(boolean enabled) {
try {
- mService.setAutoPictureQualityEnabled(enabled, mUserHandle);
+ mService.setAutoPictureQualityEnabled(enabled, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -741,7 +737,7 @@ public final class MediaQualityManager {
*/
public boolean isAutoPictureQualityEnabled() {
try {
- return mService.isAutoPictureQualityEnabled(mUserHandle);
+ return mService.isAutoPictureQualityEnabled(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -758,7 +754,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public void setSuperResolutionEnabled(boolean enabled) {
try {
- mService.setSuperResolutionEnabled(enabled, mUserHandle);
+ mService.setSuperResolutionEnabled(enabled, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -769,7 +765,7 @@ public final class MediaQualityManager {
*/
public boolean isSuperResolutionEnabled() {
try {
- return mService.isSuperResolutionEnabled(mUserHandle);
+ return mService.isSuperResolutionEnabled(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -787,7 +783,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public void setAutoSoundQualityEnabled(boolean enabled) {
try {
- mService.setAutoSoundQualityEnabled(enabled, mUserHandle);
+ mService.setAutoSoundQualityEnabled(enabled, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -798,7 +794,7 @@ public final class MediaQualityManager {
*/
public boolean isAutoSoundQualityEnabled() {
try {
- return mService.isAutoSoundQualityEnabled(mUserHandle);
+ return mService.isAutoSoundQualityEnabled(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -847,7 +843,7 @@ public final class MediaQualityManager {
@NonNull AmbientBacklightSettings settings) {
Preconditions.checkNotNull(settings);
try {
- mService.setAmbientBacklightSettings(settings, mUserHandle);
+ mService.setAmbientBacklightSettings(settings, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -858,7 +854,7 @@ public final class MediaQualityManager {
*/
public boolean isAmbientBacklightEnabled() {
try {
- return mService.isAmbientBacklightEnabled(mUserHandle);
+ return mService.isAmbientBacklightEnabled(mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -872,7 +868,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
public void setAmbientBacklightEnabled(boolean enabled) {
try {
- mService.setAmbientBacklightEnabled(enabled, mUserHandle);
+ mService.setAmbientBacklightEnabled(enabled, mUserHandle.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/quality/SoundProfileHandle.java b/media/java/android/media/quality/SoundProfileHandle.java
deleted file mode 100644
index edb546efdaf3..000000000000
--- a/media/java/android/media/quality/SoundProfileHandle.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.media.quality;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A type-safe handle to a sound profile.
- *
- * @hide
- */
-public final class SoundProfileHandle implements Parcelable {
- public static final @NonNull SoundProfileHandle NONE = new SoundProfileHandle(-1000);
-
- private final long mId;
-
- /** @hide */
- public SoundProfileHandle(long id) {
- mId = id;
- }
-
- /** @hide */
- public long getId() {
- return mId;
- }
-
- /** @hide */
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeLong(mId);
- }
-
- /** @hide */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** @hide */
- public static final @NonNull Creator<SoundProfileHandle> CREATOR =
- new Creator<SoundProfileHandle>() {
- @Override
- public SoundProfileHandle createFromParcel(Parcel in) {
- return new SoundProfileHandle(in);
- }
-
- @Override
- public SoundProfileHandle[] newArray(int size) {
- return new SoundProfileHandle[size];
- }
- };
-
- private SoundProfileHandle(@NonNull Parcel in) {
- mId = in.readLong();
- }
-}
diff --git a/media/java/android/media/quality/ActiveProcessingPicture.aidl b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl
index 2851306f6e4d..d2cf140632ab 100644
--- a/media/java/android/media/quality/ActiveProcessingPicture.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable ActiveProcessingPicture; \ No newline at end of file
+parcelable ActiveProcessingPicture cpp_header "quality/MediaQualityManager.h"; \ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl
index 174cd461e846..d53860fdf9ad 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable AmbientBacklightEvent;
+parcelable AmbientBacklightEvent cpp_header "quality/MediaQualityManager.h";
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl
index b95a474fbf90..a935b49b5d23 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable AmbientBacklightMetadata; \ No newline at end of file
+parcelable AmbientBacklightMetadata cpp_header "quality/MediaQualityManager.h"; \ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl
index e2cdd03194cd..051aef80b948 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable AmbientBacklightSettings;
+parcelable AmbientBacklightSettings cpp_header "quality/MediaQualityManager.h";
diff --git a/media/java/android/media/quality/IAmbientBacklightCallback.aidl b/media/java/android/media/quality/aidl/android/media/quality/IAmbientBacklightCallback.aidl
index 159f5b7b5e71..159f5b7b5e71 100644
--- a/media/java/android/media/quality/IAmbientBacklightCallback.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/IAmbientBacklightCallback.aidl
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl
index 6e9fa1dcf93d..0191ea786de0 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl
@@ -25,57 +25,57 @@ import android.media.quality.PictureProfileHandle;
import android.media.quality.PictureProfile;
import android.media.quality.SoundProfileHandle;
import android.media.quality.SoundProfile;
-import android.os.Bundle;
-import android.os.UserHandle;
/**
* Interface for Media Quality Manager
* @hide
*/
interface IMediaQualityManager {
- PictureProfile createPictureProfile(in PictureProfile pp, in UserHandle user);
- void updatePictureProfile(in String id, in PictureProfile pp, in UserHandle user);
- void removePictureProfile(in String id, in UserHandle user);
- boolean setDefaultPictureProfile(in String id, in UserHandle user);
+ // TODO: use UserHandle
+ PictureProfile createPictureProfile(in PictureProfile pp, int userId);
+ void updatePictureProfile(in String id, in PictureProfile pp, int userId);
+ void removePictureProfile(in String id, int userId);
+ boolean setDefaultPictureProfile(in String id, int userId);
+ // TODO: use Bundle for includeParams
PictureProfile getPictureProfile(
- in int type, in String name, in Bundle options, in UserHandle user);
+ in int type, in String name, in boolean includeParams, int userId);
List<PictureProfile> getPictureProfilesByPackage(
- in String packageName, in Bundle options, in UserHandle user);
- List<PictureProfile> getAvailablePictureProfiles(in Bundle options, in UserHandle user);
- List<String> getPictureProfilePackageNames(in UserHandle user);
- List<String> getPictureProfileAllowList(in UserHandle user);
- void setPictureProfileAllowList(in List<String> packages, in UserHandle user);
- List<PictureProfileHandle> getPictureProfileHandle(in String[] id, in UserHandle user);
+ in String packageName, in boolean includeParams, int userId);
+ List<PictureProfile> getAvailablePictureProfiles(in boolean includeParams, int userId);
+ List<String> getPictureProfilePackageNames(int userId);
+ List<String> getPictureProfileAllowList(int userId);
+ void setPictureProfileAllowList(in List<String> packages, int userId);
+ List<PictureProfileHandle> getPictureProfileHandle(in String[] id, int userId);
- SoundProfile createSoundProfile(in SoundProfile pp, in UserHandle user);
- void updateSoundProfile(in String id, in SoundProfile pp, in UserHandle user);
- void removeSoundProfile(in String id, in UserHandle user);
- boolean setDefaultSoundProfile(in String id, in UserHandle user);
+ SoundProfile createSoundProfile(in SoundProfile pp, int userId);
+ void updateSoundProfile(in String id, in SoundProfile pp, int userId);
+ void removeSoundProfile(in String id, int userId);
+ boolean setDefaultSoundProfile(in String id, int userId);
SoundProfile getSoundProfile(
- in int type, in String name, in Bundle options, in UserHandle user);
+ in int type, in String name, in boolean includeParams, int userId);
List<SoundProfile> getSoundProfilesByPackage(
- in String packageName, in Bundle options, in UserHandle user);
- List<SoundProfile> getAvailableSoundProfiles(in Bundle options, in UserHandle user);
- List<String> getSoundProfilePackageNames(in UserHandle user);
- List<String> getSoundProfileAllowList(in UserHandle user);
- void setSoundProfileAllowList(in List<String> packages, in UserHandle user);
- List<SoundProfileHandle> getSoundProfileHandle(in String[] id, in UserHandle user);
+ in String packageName, in boolean includeParams, int userId);
+ List<SoundProfile> getAvailableSoundProfiles(in boolean includeParams, int userId);
+ List<String> getSoundProfilePackageNames(int userId);
+ List<String> getSoundProfileAllowList(int userId);
+ void setSoundProfileAllowList(in List<String> packages, int userId);
+ List<SoundProfileHandle> getSoundProfileHandle(in String[] id, int userId);
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
- List<ParameterCapability> getParameterCapabilities(in List<String> names, in UserHandle user);
+ List<ParameterCapability> getParameterCapabilities(in List<String> names, int userId);
- boolean isSupported(in UserHandle user);
- void setAutoPictureQualityEnabled(in boolean enabled, in UserHandle user);
- boolean isAutoPictureQualityEnabled(in UserHandle user);
- void setSuperResolutionEnabled(in boolean enabled, in UserHandle user);
- boolean isSuperResolutionEnabled(in UserHandle user);
- void setAutoSoundQualityEnabled(in boolean enabled, in UserHandle user);
- boolean isAutoSoundQualityEnabled(in UserHandle user);
+ boolean isSupported(int userId);
+ void setAutoPictureQualityEnabled(in boolean enabled, int userId);
+ boolean isAutoPictureQualityEnabled(int userId);
+ void setSuperResolutionEnabled(in boolean enabled, int userId);
+ boolean isSuperResolutionEnabled(int userId);
+ void setAutoSoundQualityEnabled(in boolean enabled, int userId);
+ boolean isAutoSoundQualityEnabled(int userId);
- void setAmbientBacklightSettings(in AmbientBacklightSettings settings, in UserHandle user);
- void setAmbientBacklightEnabled(in boolean enabled, in UserHandle user);
- boolean isAmbientBacklightEnabled(in UserHandle user);
+ void setAmbientBacklightSettings(in AmbientBacklightSettings settings, int userId);
+ void setAmbientBacklightEnabled(in boolean enabled, int userId);
+ boolean isAmbientBacklightEnabled(int userId);
}
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/aidl/android/media/quality/IPictureProfileCallback.aidl
index eed77f695416..eed77f695416 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/IPictureProfileCallback.aidl
diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/aidl/android/media/quality/ISoundProfileCallback.aidl
index 3871fb212259..3871fb212259 100644
--- a/media/java/android/media/quality/ISoundProfileCallback.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/ISoundProfileCallback.aidl
diff --git a/media/java/android/media/quality/ParameterCapability.aidl b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl
index eb2ac97916f3..ea848576e026 100644
--- a/media/java/android/media/quality/ParameterCapability.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable ParameterCapability;
+parcelable ParameterCapability cpp_header "quality/MediaQualityManager.h";
diff --git a/media/java/android/media/quality/PictureProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl
index 41d018b12f33..b0fe3f5538f4 100644
--- a/media/java/android/media/quality/PictureProfile.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable PictureProfile;
+parcelable PictureProfile cpp_header "quality/MediaQualityManager.h";
diff --git a/media/java/android/media/quality/PictureProfileHandle.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl
index 5d14631dbb73..0582938b6ea7 100644
--- a/media/java/android/media/quality/PictureProfileHandle.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable PictureProfileHandle;
+parcelable PictureProfileHandle cpp_header "quality/MediaQualityManager.h";
diff --git a/media/java/android/media/quality/SoundProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl
index e79fcaac97be..d93231fbf7e0 100644
--- a/media/java/android/media/quality/SoundProfile.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl
@@ -16,4 +16,4 @@
package android.media.quality;
-parcelable SoundProfile;
+parcelable SoundProfile cpp_header "quality/MediaQualityManager.h";
diff --git a/media/java/android/media/quality/SoundProfileHandle.aidl b/media/java/android/media/quality/aidl/android/media/quality/SoundProfileHandle.aidl
index 6b8161c8cc43..ea26b19d84d7 100644
--- a/media/java/android/media/quality/SoundProfileHandle.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/SoundProfileHandle.aidl
@@ -16,4 +16,7 @@
package android.media.quality;
-parcelable SoundProfileHandle;
+// TODO: add SoundProfileHandle.java
+parcelable SoundProfileHandle {
+ long id;
+}
diff --git a/media/java/android/media/quality/include/quality/MediaQualityManager.h b/media/java/android/media/quality/include/quality/MediaQualityManager.h
new file mode 100644
index 000000000000..8c31667077c3
--- /dev/null
+++ b/media/java/android/media/quality/include/quality/MediaQualityManager.h
@@ -0,0 +1,127 @@
+#ifndef ANDROID_MEDIA_QUALITY_MANAGER_H
+#define ANDROID_MEDIA_QUALITY_MANAGER_H
+
+
+namespace android {
+namespace media {
+namespace quality {
+
+// TODO: implement writeToParcel and readFromParcel
+
+class PictureProfileHandle : public Parcelable {
+ public:
+ PictureProfileHandle() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class SoundProfile : public Parcelable {
+ public:
+ SoundProfile() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class PictureProfile : public Parcelable {
+ public:
+ PictureProfile() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class ActiveProcessingPicture : public Parcelable {
+ public:
+ ActiveProcessingPicture() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class AmbientBacklightEvent : public Parcelable {
+ public:
+ AmbientBacklightEvent() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class AmbientBacklightMetadata : public Parcelable {
+ public:
+ AmbientBacklightMetadata() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class AmbientBacklightSettings : public Parcelable {
+ public:
+ AmbientBacklightSettings() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+class ParameterCapability : public Parcelable {
+ public:
+ ParameterCapability() {}
+ status_t writeToParcel(android::Parcel*) const override {
+ return 0;
+ }
+ status_t readFromParcel(const android::Parcel*) override {
+ return 0;
+ }
+ std::string toString() const {
+ return "";
+ }
+};
+
+} // namespace quality
+} // namespace media
+} // namespace android
+
+#endif
diff --git a/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl
index cdf6e23f4b47..40848fe6f875 100644
--- a/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl
+++ b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl
@@ -21,5 +21,5 @@ package android.media.tv.extension.scan;
*/
interface IHDPlusInfo {
// Specifying a HDPlusInfo and start a network scan.
- int setHDPlusInfo(String isBlindScanContinue, String isHDMode);
+ int setHDPlusInfo(boolean isBlindScanContinue, boolean isHDMode);
}
diff --git a/media/java/android/media/tv/extension/scan/IScanListener.aidl b/media/java/android/media/tv/extension/scan/IScanListener.aidl
index 2c4807f97c58..79810a7b035d 100644
--- a/media/java/android/media/tv/extension/scan/IScanListener.aidl
+++ b/media/java/android/media/tv/extension/scan/IScanListener.aidl
@@ -27,7 +27,7 @@ oneway interface IScanListener {
// notify the scan progress.
void onScanProgress(String scanProgress, in Bundle scanProgressInfo);
// notify the scan completion.
- void onScanCompleted(int scanResult);
+ void onScanCompleted(int scanResult, in Bundle optionScanInfo);
// notify that the temporaily held channel list is stored.
void onStoreCompleted(int storeResult);
}
diff --git a/media/java/android/media/tv/extension/scan/IScanSession.aidl b/media/java/android/media/tv/extension/scan/IScanSession.aidl
index d42eca1342b5..f53b09661ba6 100644
--- a/media/java/android/media/tv/extension/scan/IScanSession.aidl
+++ b/media/java/android/media/tv/extension/scan/IScanSession.aidl
@@ -24,7 +24,7 @@ import android.os.Bundle;
interface IScanSession {
// Start a service scan.
int startScan(int broadcastType, String countryCode, String operator, in int[] frequency,
- String scanType, String languageCode);
+ String scanType, String languageCode, in Bundle optionalScanParams);
// Reset the scan information held in TIS.
int resetScan();
// Cancel scan.
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl
index 1b1577ffc015..0e9ac5f04e5d 100644
--- a/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl
@@ -78,4 +78,11 @@ interface IServiceListEdit {
int addPredefinedChannelList(String serviceListId, in Bundle[] predefinedListBundle);
// Add predefined satellite info of Hotbird 13E in scan two satellite scene EU region.
int addPredefinedSatInfo(String serviceListId, in Bundle predefinedSatInfoBundle);
+
+ // Get the logo URI for a specific service - DVB-I only.
+ String getServiceLogoUri(int serviceRecordId);
+ // Get the installed service list information for a specific channel list id - DVB-I only.
+ Bundle getInstalledServiceListInfo(String channelListId);
+ // Get all installed service list information - DVB-I only.
+ Bundle[] getAllInstalledServiceListInfo();
}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl
index abd8320df11d..fced45b11d3b 100644
--- a/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl
@@ -16,10 +16,12 @@
package android.media.tv.extension.servicedb;
+import android.os.Bundle;
+
/**
* @hide
*/
interface IServiceListImportListener {
void onImported(int importResult);
- void onPreloaded(int preloadResult);
+ void onPreloaded(int preloadResult, in Bundle serviceListInfo);
} \ No newline at end of file
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
index 6089f4291f3e..f65c7efa8ca7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -28,7 +28,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.companion.virtual.VirtualDeviceManager;
@@ -56,7 +56,7 @@ public class AudioManagerUnitTest {
audioManager.playSoundEffect(FX_KEY_CLICK);
// We expect no interactions with VDM when running on default device.
- verifyZeroInteractions(mockVdm);
+ verifyNoMoreInteractions(mockVdm);
}
@Test
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
index 65264d30f04f..006b86a63f60 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
@@ -16,9 +16,9 @@
package com.android.mediaframeworktest.unit;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 1e6a7b7f2810..45b746d254e1 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -971,7 +971,7 @@ void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkD
.timeStampNanos = (i == count - 1) ? now : message.timeStampNanos,
.data = HalChannelMessageContents::make<HalChannelMessageContents::workDuration,
hal::WorkDurationFixedV1>({
- .durationNanos = message.cpuDurationNanos,
+ .durationNanos = message.durationNanos,
.workPeriodStartTimestampNanos = message.workPeriodStartTimestampNanos,
.cpuDurationNanos = message.cpuDurationNanos,
.gpuDurationNanos = message.gpuDurationNanos,
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 275972495206..f6dc41ed128a 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -89,7 +89,7 @@ ASurfaceControl* ASurfaceControl_createFromWindow(ANativeWindow* window, const c
CHECK_NOT_NULL(window);
CHECK_NOT_NULL(debug_name);
- sp<SurfaceComposerClient> client = new SurfaceComposerClient();
+ sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
if (client->initCheck() != NO_ERROR) {
return nullptr;
}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
index 6229434d1d86..7c54ad2f231c 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
@@ -16,7 +16,7 @@
package com.android.carrierdefaultapp;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index b802d0f69a08..8e5a2b7c3663 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Kanselleer"</string>
<string name="consent_back" msgid="2560683030046918882">"Terug"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Rollees met die lys af"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Afwaartse pyl"</string>
<string name="permission_expand" msgid="893185038020887411">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uit"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> in"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Gee programme op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dieselfde toestemmings as op &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index d4d0b0708302..40472d253072 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ይቅር"</string>
<string name="consent_back" msgid="2560683030046918882">"ተመለስ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"በዝርዝሩ ላይ ወደታች ያሸብልሉ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"የአውርድ ቀስት"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ዘርጋ"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ሰብስብ"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"በ&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ላይ ላሉ መተግበሪያዎች በ&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ላይ ካሉት ጋር ተመሳሳይ ፈቃዶች ይሰጣቸው?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index f4e17213fd2d..40c7dd16812b 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
<string name="consent_cancel" msgid="5655005528379285841">"إلغاء"</string>
<string name="consent_back" msgid="2560683030046918882">"رجوع"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"الانتقال لأسفل القائمة"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"سهم متّجه للأسفل"</string>
<string name="permission_expand" msgid="893185038020887411">"توسيع <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"تصغير <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏هل تريد منح التطبيقات على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; نفس الأذونات على &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;؟"</string>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index 322d422f60d7..1f3ee732c8ce 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string>
<string name="consent_cancel" msgid="5655005528379285841">"বাতিল কৰক"</string>
<string name="consent_back" msgid="2560683030046918882">"উভতি যাওক"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"সূচীখনত তললৈ স্ক্ৰ’ল কৰক"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"তলমুৱা কাঁড়"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> সংকোচন কৰক"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"এপ্‌সমূহক &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ত দিয়াৰ দৰে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;তো একে অনুমতি প্ৰদান কৰিবনে?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index 9f3ab2bd50cd..aa4788ac7e1e 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Ləğv edin"</string>
<string name="consent_back" msgid="2560683030046918882">"Geriyə"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Siyahını aşağı sürüşdürün"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Aşağı ox"</string>
<string name="permission_expand" msgid="893185038020887411">"Genişləndirin: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Yığcamlaşdırın: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazındakı tətbiqlərə &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazındakılarla eyni icazələr verilsin?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index d28b9b26fb07..3dcb585a0bc2 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string>
<string name="consent_back" msgid="2560683030046918882">"Nazad"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Skrolujte nadole na listi"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Strelica nadole"</string>
<string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Skupi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Aplikcijama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dajete sve dozvole kao na uređaju &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 14878c11c5db..8ef80d34e37d 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Скасаваць"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Прагартаць спіс уніз"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрэлка ўніз"</string>
<string name="permission_expand" msgid="893185038020887411">"Разгарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Згарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Даць праграмам на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; такія самыя дазволы, што і на прыладзе &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 6e5b3234b992..ebc2cf5dc9f9 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Отказ"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Превъртете списъка надолу"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрелка за надолу"</string>
<string name="permission_expand" msgid="893185038020887411">"Разгъване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Свиване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Искате ли да дадете на приложенията на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; същите разрешения както на &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 214dc71a6a88..f5cf720a64a5 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
<string name="consent_cancel" msgid="5655005528379285841">"বাতিল করুন"</string>
<string name="consent_back" msgid="2560683030046918882">"ফিরুন"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"তালিকা নিচের দিকে স্ক্রল করুন"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"নিম্নমুখী তীরচিহ্ন"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বড় করুন"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> আড়াল করুন"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-এ যে অনুমতি দেওয়া আছে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-এও সেই একই অনুমতি দিতে চান?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index b23f600cbcf0..a87ea18f831c 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string>
<string name="consent_back" msgid="2560683030046918882">"Nazad"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Klizanje nadolje na listi"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Strelica nadolje"</string>
<string name="permission_expand" msgid="893185038020887411">"Proširivanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Sužavanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dati aplikacijama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ista odobrenja kao na uređaju &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index 21f3871d8209..7ec2b8b0dab6 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancel·la"</string>
<string name="consent_back" msgid="2560683030046918882">"Enrere"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Desplaça la llista cap avall"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Fletxa avall"</string>
<string name="permission_expand" msgid="893185038020887411">"Desplega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Replega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vols concedir a les aplicacions del dispositiu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; els mateixos permisos que tenen a &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index cd8913012eeb..a1ea39b26eeb 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Zrušit"</string>
<string name="consent_back" msgid="2560683030046918882">"Zpět"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Přejít v seznamu dolů"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Šipka dolů"</string>
<string name="permission_expand" msgid="893185038020887411">"Rozbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Sbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Udělit aplikacím v zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; stejné oprávnění, jako mají v zařízení &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index 6dc34e77c5ce..97e6fe563dd6 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annuller"</string>
<string name="consent_back" msgid="2560683030046918882">"Tilbage"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Rul ned på listen"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Pil ned"</string>
<string name="permission_expand" msgid="893185038020887411">"Udvid <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vil du give apps på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; de samme tilladelser som på &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 4dc292916628..fc0231f24445 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Abbrechen"</string>
<string name="consent_back" msgid="2560683030046918882">"Zurück"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"In der Liste nach unten scrollen"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Abwärtspfeil"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> maximieren"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> minimieren"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Apps auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; die gleichen Berechtigungen geben wie auf &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 1dc00dc4085b..9ede68477cfa 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Ακύρωση"</string>
<string name="consent_back" msgid="2560683030046918882">"Πίσω"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Κύλιση προς τα κάτω στη λίστα"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Βέλος που δείχνει προς τα κάτω"</string>
<string name="permission_expand" msgid="893185038020887411">"Ανάπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Σύμπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Παραχώρηση των ίδιων αδειών στις εφαρμογές στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; όπως στη συσκευή &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;;"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index bbd81bf68b9d..68c74e0e82f8 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index 8d7338024107..15d30429c25f 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index bbd81bf68b9d..68c74e0e82f8 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index bbd81bf68b9d..68c74e0e82f8 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 50f76549d807..edcc4bd465d3 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Bajar por la lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Flecha hacia abajo"</string>
<string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"¿Dar a las apps de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; los mismos permisos que tienen en &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index f8e2c742cccc..d74b4f822c15 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Desplazarse hacia abajo por la lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Flecha hacia abajo"</string>
<string name="permission_expand" msgid="893185038020887411">"Desplegar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"¿Dar a las aplicaciones de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; los mismos permisos que &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 0f390e1fea89..3c837582027a 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Tühista"</string>
<string name="consent_back" msgid="2560683030046918882">"Tagasi"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Kerige loendis alla"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Allapoole suunatud nool"</string>
<string name="permission_expand" msgid="893185038020887411">"Laienda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Ahenda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Kas anda rakendustele seadmes &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; samad load, mis seadmes &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index fb36c46d2d6a..cf4fe25ab0d0 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"اجازه ندادن"</string>
<string name="consent_cancel" msgid="5655005528379285841">"لغو"</string>
<string name="consent_back" msgid="2560683030046918882">"برگشتن"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"پیمایش به‌پایین فهرست"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"جهت‌نمای پایین"</string>
<string name="permission_expand" msgid="893185038020887411">"ازهم بازکردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"جمع کردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏به برنامه‌های موجود در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; همان اجازه‌های &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; داده شود؟"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index ff6cae1bbe3c..4be6a5acbf34 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Peruuta"</string>
<string name="consent_back" msgid="2560683030046918882">"Takaisin"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Vieritä listaa alaspäin"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Alanuoli"</string>
<string name="permission_expand" msgid="893185038020887411">"Laajenna <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Tiivistä <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Anna laitteen &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; sovelluksille samat luvat kuin laitteella &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index 4d7de76334de..70beafba782c 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string>
<string name="consent_back" msgid="2560683030046918882">"Retour"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Faire défiler la liste en bas"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Flèche vers le bas"</string>
<string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Accorder aux applis sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; les autorisations déjà accordées sur &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index d4c12fba1492..87148207575b 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -26,7 +26,7 @@
<string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
<string name="chooser_title_non_profile" msgid="6035023914517087400">"Sélectionner l\'appareil qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
<string name="chooser_title" msgid="2235819929238267637">"Sélectionner votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g> à configurer"</string>
- <string name="single_device_title" msgid="4199861437545438606">"Recherche de l\'appareil suivant : <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+ <string name="single_device_title" msgid="4199861437545438606">"Recherche de votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
<string name="summary_watch" msgid="8134580124808507407">"Cette appli sera autorisée à synchroniser des infos (comme le nom de l\'appelant) et disposera de ces autorisations sur votre <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
<string name="confirmation_title_glasses" msgid="8288346850537727333">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
<string name="profile_name_glasses" msgid="3506504967216601277">"appareil"</string>
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string>
<string name="consent_back" msgid="2560683030046918882">"Retour"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Faire défiler la liste vers le bas"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Flèche vers le bas"</string>
<string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Accorder les mêmes autorisations aux applis sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; que sur &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index 999ab9741084..ce23be8e8fbb 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Desprazarse abaixo na lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Frecha cara abaixo"</string>
<string name="permission_expand" msgid="893185038020887411">"Despregar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Queres darlles ás aplicacións de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; os mesmos permisos que teñen as de &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 51d07c97fa5c..7956ba6814ff 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
<string name="consent_cancel" msgid="5655005528379285841">"રદ કરો"</string>
<string name="consent_back" msgid="2560683030046918882">"પાછળ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"સૂચિ પર નીચે સ્ક્રોલ કરો"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"નીચેની તરફ ઍરો"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને મોટું કરો"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને નાનું કરો"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; પરની ઍપને &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; પર છે તે જ પરવાનગીઓ આપીએ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index f7086c650e2e..f204e9450568 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
<string name="consent_cancel" msgid="5655005528379285841">"रद्द करें"</string>
<string name="consent_back" msgid="2560683030046918882">"वापस जाएं"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"सूची को नीचे की ओर स्क्रोल करें"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"डाउनवर्ड ऐरो"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को बड़ा करें"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को छोटा करें"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"क्या &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; पर ऐप्लिकेशन को वही अनुमतियां देनी हैं जो &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; पर दी हैं?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index 04872e53cb36..326564e90f5e 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Odustani"</string>
<string name="consent_back" msgid="2560683030046918882">"Natrag"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Pomicanje po popisu"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Strelica prema dolje"</string>
<string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Sažmi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dati jednaka dopuštenja aplikacijama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; kao i na uređaju &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index 5d3b31329198..42702ca00f1c 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Mégse"</string>
<string name="consent_back" msgid="2560683030046918882">"Vissza"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Görgetés a listában lefelé"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Lefelé mutató nyíl"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> kibontása"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> összecsukása"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ugyanolyan engedélyeket ad a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; eszközön található alkalmazásoknak, mint a(z) &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; eszköz esetén?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index e7c71391fb91..cbf1d8d7f921 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Չեղարկել"</string>
<string name="consent_back" msgid="2560683030046918882">"Հետ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Թերթեք ներքև"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Ներքև սլաք"</string>
<string name="permission_expand" msgid="893185038020887411">"Ծավալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Ծալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-ում հավելվածներին տա՞լ նույն թույլտվությունները, ինչ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-ում"</string>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index f58220563e4e..fe3406ecd6be 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Batal"</string>
<string name="consent_back" msgid="2560683030046918882">"Kembali"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll daftar ke bawah"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Panah bawah"</string>
<string name="permission_expand" msgid="893185038020887411">"Luaskan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Ciutkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Berikan aplikasi di &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; izin yang sama seperti di &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index b66133cddeb6..9e366d6ec3a9 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Hætta við"</string>
<string name="consent_back" msgid="2560683030046918882">"Til baka"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Fletta niður listann"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Ör niður"</string>
<string name="permission_expand" msgid="893185038020887411">"Stækka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Minnka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Veita forritum í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; sömu heimildir og í &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 34967181eea7..b744542f2f89 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annulla"</string>
<string name="consent_back" msgid="2560683030046918882">"Indietro"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scorri l\'elenco verso il basso"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Freccia verso il basso"</string>
<string name="permission_expand" msgid="893185038020887411">"Espandi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Comprimi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vuoi dare alle app su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; le stesse autorizzazioni che hai dato su &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 626ae7a3b50e..1e299b72ffb6 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ביטול"</string>
<string name="consent_back" msgid="2560683030046918882">"חזרה"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"גלילה למטה ברשימה"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"חץ למטה"</string>
<string name="permission_expand" msgid="893185038020887411">"הרחבה של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"כיווץ של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏האם לתת לאפליקציות ב-‎&lt;strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>‏‎&lt;/strong&gt;‎‏ את אותן הרשאות כמו ב-‏‎&lt;strong&gt;‎‏<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>‏‎&lt;/strong&gt;‎‏?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index a8dc756fee76..664d53630cc9 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
<string name="consent_cancel" msgid="5655005528379285841">"キャンセル"</string>
<string name="consent_back" msgid="2560683030046918882">"戻る"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"リストを下にスクロール"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"下矢印"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を開く"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を閉じる"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; のアプリに &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; の場合と同じ権限を付与しますか?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index 97f3087663cf..d0214aeb13ca 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
<string name="consent_cancel" msgid="5655005528379285841">"გაუქმება"</string>
<string name="consent_back" msgid="2560683030046918882">"უკან"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"გადაადგილება სიის ქვემოთ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ქვემოთ მიმართული ისარი"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის გაფართოება"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის ჩაკეცვა"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"გსურთ აპებს &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-ზე იგივე ნებართვები მიანიჭოთ, როგორიც აქვს &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-ზე?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 4d77c6cc588c..e7944154b516 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Бас тарту"</string>
<string name="consent_back" msgid="2560683030046918882">"Артқа"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Тізім соңына дейін айналдыру"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Төмен бағытталған перне"</string>
<string name="permission_expand" msgid="893185038020887411">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жаю"</string>
<string name="permission_collapse" msgid="3320833884220844084">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жию"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; құрылғысындағы қолданбаларға &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; құрылғысындағыдай рұқсаттар берілсін бе?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 9047b05eb373..ddd414422199 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"មិនអនុញ្ញាត"</string>
<string name="consent_cancel" msgid="5655005528379285841">"បោះបង់"</string>
<string name="consent_back" msgid="2560683030046918882">"ថយក្រោយ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"រំកិលបញ្ជីចុះក្រោម"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ព្រួញ​ចុះ​ក្រោម"</string>
<string name="permission_expand" msgid="893185038020887411">"ពង្រីក <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"បង្រួម <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ផ្ដល់​ការអនុញ្ញាតឱ្យ​កម្មវិធីនៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ដូចនៅលើ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ឬ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index 751ac1f1a5ad..a957f48f1a13 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ರದ್ದುಮಾಡಿ"</string>
<string name="consent_back" msgid="2560683030046918882">"ಹಿಂದೆ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"ಪಟ್ಟಿಯನ್ನು ಕೆಳಗೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ಕೆಳಮುಖ ಬಾಣ"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತರಿಸಿ"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;/strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ಅನುಮತಿಗಳನ್ನೇ &lt;/strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳಿಗೆ ನೀಡಬೇಕೆ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index a7a5dc1bd204..d7d489af9755 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
<string name="consent_cancel" msgid="5655005528379285841">"취소"</string>
<string name="consent_back" msgid="2560683030046918882">"뒤로"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"목록을 아래로 스크롤"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"아래쪽 화살표"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 펼치기"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 접기"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;에 설치된 앱에 &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;에 설치된 앱과 동일한 권한을 부여하시겠습니까?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 991fc9f13248..3f8c58af129a 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Жок"</string>
<string name="consent_back" msgid="2560683030046918882">"Артка"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Тизменин ылдый жагына сыдыруу"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Ылдый караган жебе"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жайып көрсөтүү"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жыйыштыруу"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; түзмөгүнө да &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүнө берилген уруксаттар берилсинби?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index 91399b60f7b7..457efdb6f864 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ຍົກເລີກ"</string>
<string name="consent_back" msgid="2560683030046918882">"ກັບຄືນ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"ເລື່ອນລາຍຊື່ລົງ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ລູກສອນຊີ້ລົງ"</string>
<string name="permission_expand" msgid="893185038020887411">"ຂະຫຍາຍ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"ຫຍໍ້ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ລົງ"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ໃຫ້ການອະນຸຍາດແອັບຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ເປັນການອະນຸຍາດດຽວກັນກັບຢູ່ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ບໍ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index a3ac47b9642f..09183bbd9756 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Atšaukti"</string>
<string name="consent_back" msgid="2560683030046918882">"Atgal"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Slinkti sąrašu žemyn"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Rodyklė žemyn"</string>
<string name="permission_expand" msgid="893185038020887411">"Išskleisti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Sutraukti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Suteikti &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; esančioms programoms tuos pačius leidimus kaip &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; esančioms programoms?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index 35c748125180..01c9ed5e4657 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Atcelt"</string>
<string name="consent_back" msgid="2560683030046918882">"Atpakaļ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Ritināt sarakstu lejup"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Lejupvērsta bultiņa"</string>
<string name="permission_expand" msgid="893185038020887411">"Izvērst: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Sakļaut: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vai lietotnēm ierīcē &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; piešķirt tādas pašas atļaujas kā ierīcē &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 1eaf2d23690b..d9c8695d7c13 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Лизгај надолу по списокот"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрелка надолу"</string>
<string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Собери <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Дасе дадат исти дозволи на апликациите на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; како на &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index 52fcb1f9a7b6..828b6cfac332 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
<string name="consent_cancel" msgid="5655005528379285841">"റദ്ദാക്കുക"</string>
<string name="consent_back" msgid="2560683030046918882">"മടങ്ങുക"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"ലിസ്റ്റ് താഴേക്ക് സ്ക്രോൾ ചെയ്യൂ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"താഴേക്കുള്ള അമ്പടയാളം"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ചുരുക്കുക"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; എന്നതിലെ അതേ അനുമതികൾ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിലെ ആപ്പുകൾക്ക് നൽകണോ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 5faf241c81a1..be6eb8e6fe58 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Цуцлах"</string>
<string name="consent_back" msgid="2560683030046918882">"Буцах"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Жагсаалтыг доош гүйлгэх"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Доош заасан сум"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г дэлгэх"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г хураах"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; дээрх аппуудад &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; дээрхтэй адил зөвшөөрөл өгөх үү?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 94e49fecd7e0..99f59a641b89 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
<string name="consent_cancel" msgid="5655005528379285841">"रद्द करा"</string>
<string name="consent_back" msgid="2560683030046918882">"मागे जा"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"सूचीवर खाली स्क्रोल करा"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"खालच्या दिशेचा अ‍ॅरो"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> चा विस्तार करा"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोलॅप्स करा"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; वरील अ‍ॅप्सना &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रमाणेच परवानग्या द्यायच्या आहेत का?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index 8b1170ba03d2..9c15c0607b7b 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Batal"</string>
<string name="consent_back" msgid="2560683030046918882">"Kembali"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Tatal ke bawah senarai"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Anak panah ke bawah"</string>
<string name="permission_expand" msgid="893185038020887411">"Kembangkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Kuncupkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Beri apl pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; kebenaran yang sama seperti pada &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index faca3e35d549..6068c43b5274 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
<string name="consent_cancel" msgid="5655005528379285841">"မလုပ်တော့"</string>
<string name="consent_back" msgid="2560683030046918882">"နောက်သို့"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"စာရင်းအောက်သို့ လှိမ့်ရန်"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"အောက်ညွှန်မြား"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို ပိုပြရန်"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို လျှော့ပြရန်"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"အက်ပ်များကို &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; တွင်ပေးထားသည့် ခွင့်ပြုချက်များအတိုင်း &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; တွင် ပေးမလား။"</string>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 4188951a15ed..eb5e2e996bdc 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string>
<string name="consent_back" msgid="2560683030046918882">"Tilbake"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Rull nedover i listen"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Nedoverpil"</string>
<string name="permission_expand" msgid="893185038020887411">"Vis <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vil du gi apper på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; de samme tillatelsene som på &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index 2cd4fef2c53a..f3b56de35f12 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string>
<string name="consent_cancel" msgid="5655005528379285841">"रद्द गर्नुहोस्"</string>
<string name="consent_back" msgid="2560683030046918882">"पछाडि"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"सूचीको तलतिर स्क्रोल गर्नुहोस्"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"डाउनवार्ड एरो"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; मा भएका एपहरूलाई पनि &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; मा दिइएकै अनुमति दिने हो?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 2f0fc9a7997a..2bb4b9abce2a 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annuleren"</string>
<string name="consent_back" msgid="2560683030046918882">"Terug"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Omlaag scrollen in de lijst"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Pijl-omlaag"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uitvouwen"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> samenvouwen"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Apps op de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dezelfde rechten geven als op de &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index 8298a5b9d411..bd602e496d74 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ବାତିଲ କରନ୍ତୁ"</string>
<string name="consent_back" msgid="2560683030046918882">"ପଛକୁ ଫେରନ୍ତୁ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"ତାଲିକା ତଳକୁ ସ୍କ୍ରୋଲ କରନ୍ତୁ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ଡାଉନୱାର୍ଡ ତୀର"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ପରି &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;ରେ ଥିବା ଆପ୍ସକୁ ସମାନ ଅନୁମତିଗୁଡ଼ିକ ଦେବେ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index fe13c00dfb98..ae0b59dbc21d 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ਰੱਦ ਕਰੋ"</string>
<string name="consent_back" msgid="2560683030046918882">"ਪਿੱਛੇ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"ਸੂਚੀ ਨੂੰ ਹੇਠਾਂ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ਹੇਠਾਂ ਤੀਰ"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ਕੀ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਐਪਾਂ ਨੂੰ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਐਪਾਂ ਵਾਂਗ ਇਜਾਜ਼ਤਾਂ ਦੇਣੀਆਂ ਹਨ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 1599f3f333fe..bce0a6ebdaa5 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string>
<string name="consent_back" msgid="2560683030046918882">"Wstecz"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Przewiń listę w dół"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Strzałka w dół"</string>
<string name="permission_expand" msgid="893185038020887411">"Rozwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Zwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Czy aplikacjom na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; przyznać te same uprawnienia co na urządzeniu &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index 85c23ea7f253..35b653a4e6de 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Role para baixo na lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Seta para baixo"</string>
<string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar aos apps no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; as mesmas permissões do dispositivo &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index bee8e916349d..ec9beeb2c7ce 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Deslocar para baixo na lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Seta para baixo"</string>
<string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Reduzir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar às apps no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; as mesmas autorizações de &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index 85c23ea7f253..35b653a4e6de 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Role para baixo na lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Seta para baixo"</string>
<string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar aos apps no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; as mesmas permissões do dispositivo &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index 6820d5b44633..0fdeb29f0400 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nu permite"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Anulează"</string>
<string name="consent_back" msgid="2560683030046918882">"Înapoi"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Derulează în jos lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Săgeată în jos"</string>
<string name="permission_expand" msgid="893185038020887411">"Extinde <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Restrânge <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Acorzi aplicațiilor de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; aceleași permisiuni ca pe &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index 3213a2faa7d8..872f45eae425 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Отмена"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Прокрутить список вниз"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрелка вниз"</string>
<string name="permission_expand" msgid="893185038020887411">"Разворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string>
<string name="permission_collapse" msgid="3320833884220844084">"Сворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Предоставить приложениям на устройстве &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; те же разрешения, что на устройстве &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index 7fd66110c10b..ec362f2c33f2 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
<string name="consent_cancel" msgid="5655005528379285841">"අවලංගු කරන්න"</string>
<string name="consent_back" msgid="2560683030046918882">"ආපසු"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"ලැයිස්තුව පහළට අනුචලනය කරන්න"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"පහළට ඊතලය"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> විදහන්න"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> හකුළන්න"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the හි යෙදුම්වලට &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; හි අවසරම දෙන්නද?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index 5194f215444a..cbf56486a044 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Zrušiť"</string>
<string name="consent_back" msgid="2560683030046918882">"Späť"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Posunúť zoznam nadol"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Šípka nadol"</string>
<string name="permission_expand" msgid="893185038020887411">"Rozbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Zbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Chcete udeliť aplikáciám v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; rovnaké povolenia ako v zariadení &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 220022095677..20570c300038 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Prekliči"</string>
<string name="consent_back" msgid="2560683030046918882">"Nazaj"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Pomikanje navzdol po seznamu"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Puščica navzdol"</string>
<string name="permission_expand" msgid="893185038020887411">"Razširi dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Strni dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ali želite aplikacijam v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; odobriti enaka dovoljenja kot v napravi &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index f683dd9e2974..32ede4022156 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Anulo"</string>
<string name="consent_back" msgid="2560683030046918882">"Pas"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Lëviz poshtë në listë"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Shigjeta poshtë"</string>
<string name="permission_expand" msgid="893185038020887411">"Zgjero: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Palos: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"T\'i jepen aplikacioneve në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; të njëjtat leje si në &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index d56800958d81..ac84c345b923 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Скролујте надоле на листи"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрелица надоле"</string>
<string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Скупи <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Апликцијама на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; дајете све дозволе као на уређају &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index 5c6f5ba65525..c9364b8564ef 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string>
<string name="consent_back" msgid="2560683030046918882">"Tillbaka"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scrolla nedåt i listan"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Nedåtpil"</string>
<string name="permission_expand" msgid="893185038020887411">"Utöka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Komprimera <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vill du ge apparna på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; samma behörigheter som de har på &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 7a1cf98832a4..2a9cbee0f2ad 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Ghairi"</string>
<string name="consent_back" msgid="2560683030046918882">"Nyuma"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Sogeza chini kwenye orodha"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Kishale kinachoelekeza chini"</string>
<string name="permission_expand" msgid="893185038020887411">"Panua <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Kunja <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ungependa kuzipa programu katika &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ruhusa ile ile kama kwenye &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index 2313cc3d3b2a..2503826b9074 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ரத்துசெய்"</string>
<string name="consent_back" msgid="2560683030046918882">"பின்செல்"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"பட்டியலில் கீழே நகர்த்து"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"கீழ்நோக்கிய அம்புக்குறி"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; சாதனத்தில் இருக்கும் அதே அனுமதிகளை &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; சாதனத்தில் உள்ள ஆப்ஸுக்கும் வழங்கவா?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index eed0eeef8143..d82c57c3e8fd 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string>
<string name="consent_cancel" msgid="5655005528379285841">"రద్దు చేయండి"</string>
<string name="consent_back" msgid="2560683030046918882">"వెనుకకు"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"లిస్ట్‌ను కిందకు స్క్రోల్ చేయి"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"కింది వైపు బాణం గుర్తు"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‌ను విస్తరించండి"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‌ను కుదించండి"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;లోని యాప్‌లకు &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;లో ఉన్న అనుమతులను ఇవ్వాలా?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index bf14f4c529fd..26aa9ddc4205 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
<string name="consent_cancel" msgid="5655005528379285841">"ยกเลิก"</string>
<string name="consent_back" msgid="2560683030046918882">"กลับ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"เลื่อนรายการลง"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ลูกศรชี้ลง"</string>
<string name="permission_expand" msgid="893185038020887411">"ขยาย <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"ยุบ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ให้แอปใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; มีสิทธิ์เหมือนกับใน &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ไหม"</string>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 90e5eb7583e0..fbc12f2a5f06 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Kanselahin"</string>
<string name="consent_back" msgid="2560683030046918882">"Bumalik"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Mag-scroll pababa sa listahan"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Pababang arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"I-expand ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"I-collapse ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Bigyan ang mga app sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ng mga pahintulot na mayroon din sa &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index 56997d7a09c3..01fc767868d4 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
<string name="consent_cancel" msgid="5655005528379285841">"İptal"</string>
<string name="consent_back" msgid="2560683030046918882">"Geri"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Listeyi aşağı kaydırın"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Aşağı ok"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini genişlet"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini daralt"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazındaki uygulamalara, &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazındakiyle aynı izinler verilsin mi?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 7d7b9dedde31..73e5d585c5a9 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Скасувати"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Прокрутити список униз"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрілка вниз"</string>
<string name="permission_expand" msgid="893185038020887411">"Розгорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string>
<string name="permission_collapse" msgid="3320833884220844084">"Згорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Надати додаткам на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; такі самі дозволи, що й на пристрої &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 690e439857d1..895dfb05d264 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
<string name="consent_cancel" msgid="5655005528379285841">"منسوخ کریں"</string>
<string name="consent_back" msgid="2560683030046918882">"پیچھے"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"فہرست نیچے سکرول کریں"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"نیچے کی طرف تیر کا نشان"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو پھیلائیں"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو سکیڑیں"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏ایپس کو &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; پر وہی اجازتیں دیں جو &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; پر دی گئی ہیں؟"</string>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index e1a024c985fd..b4ac45c9ceb6 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Bekor qilish"</string>
<string name="consent_back" msgid="2560683030046918882">"Orqaga"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Roʻyxatni pastga varaqlash"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Pastga strelka"</string>
<string name="permission_expand" msgid="893185038020887411">"Yoyish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Yopish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovalariga &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasidagi kabi bir xil ruxsatlar berilsinmi?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index 029331701fd7..11cceaf2c4d6 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Huỷ"</string>
<string name="consent_back" msgid="2560683030046918882">"Quay lại"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Di chuyển xuống danh sách"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Mũi tên xuống"</string>
<string name="permission_expand" msgid="893185038020887411">"Mở rộng <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Thu gọn <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Cấp cho các ứng dụng trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; các quyền giống như trên &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index ed68dd3d08db..7632af0a3ca0 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"不允许"</string>
<string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
<string name="consent_back" msgid="2560683030046918882">"返回"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"向下滚动列表"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"向下箭头"</string>
<string name="permission_expand" msgid="893185038020887411">"展开<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"收起<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"要让&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;上的应用享有在&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;上的同等权限吗?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index 17d3482f0dbc..9f988c3ff585 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"不允許"</string>
<string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
<string name="consent_back" msgid="2560683030046918882">"返回"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"向下捲動清單"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"向下箭咀"</string>
<string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; 上的應用程式可獲在 &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; 上的相同權限嗎?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 542a73fc099b..5bc7bfa3781a 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"不允許"</string>
<string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
<string name="consent_back" msgid="2560683030046918882">"返回"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"向下捲動清單"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"向下箭頭"</string>
<string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"要讓「<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;的應用程式沿用在「<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;上的權限嗎?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index b1c7a14d1210..37034fa6718b 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Khansela"</string>
<string name="consent_back" msgid="2560683030046918882">"Emuva"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Skrola uye ezansi ohlwini"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Umcibisholo obheke phansi"</string>
<string name="permission_expand" msgid="893185038020887411">"Nweba i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Goqa i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Nikeza ama-app &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; izimvume ezifanayot &lt;strong&gt;njengaku-<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index b2c1e604db7e..964268e4ad14 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -65,6 +65,7 @@ import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.Spanned;
@@ -621,8 +622,10 @@ public class CompanionAssociationActivity extends FragmentActivity implements
Slog.w(TAG, "Already selected.");
return;
}
- // Notify the adapter to highlight the selected item.
- mDeviceAdapter.setSelectedPosition(position);
+ // Delay highlighting the selected item by posting to the main thread.
+ // This helps avoid flicker in the user consent dialog after device selection.
+ new Handler(
+ Looper.getMainLooper()).post(() -> mDeviceAdapter.setSelectedPosition(position));
mSelectedDevice = requireNonNull(selectedDevice);
diff --git a/packages/DynamicSystemInstallationService/res/values-mr/strings.xml b/packages/DynamicSystemInstallationService/res/values-mr/strings.xml
index 3b6741d6c5a8..b18ce0e1db32 100644
--- a/packages/DynamicSystemInstallationService/res/values-mr/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-mr/strings.xml
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="keyguard_description" msgid="8582605799129954556">"कृपया तुमचा पासवर्ड एंटर करा आणि डायनॅमिक सिस्टम अपडेट वर जा"</string>
- <string name="notification_install_completed" msgid="6252047868415172643">"डायनॅमिक सिस्टम तयार आहे. ती वापरणे सुरू करण्यासाठी, तुमचे डिव्हाइस रीस्टार्ट करा."</string>
+ <string name="keyguard_description" msgid="8582605799129954556">"कृपया तुमचा पासवर्ड एंटर करा आणि डायनॅमिक सिस्टीम अपडेट वर जा"</string>
+ <string name="notification_install_completed" msgid="6252047868415172643">"डायनॅमिक सिस्टीम तयार आहे. ती वापरणे सुरू करण्यासाठी, तुमचे डिव्हाइस रीस्टार्ट करा."</string>
<string name="notification_install_inprogress" msgid="7383334330065065017">"इंस्टॉल प्रगतीपथावर आहे"</string>
<string name="notification_install_failed" msgid="4066039210317521404">"इंस्टॉल करता आली नाही"</string>
<string name="notification_image_validation_failed" msgid="2720357826403917016">"इमेज प्रमाणित करता आली नाही. इंस्टॉलेशन रद्द करा."</string>
- <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"सध्या डायनॅमिक सिस्टम रन करत आहे. मूळ Android आवृत्ती वापरण्यासाठी रीस्टार्ट करा."</string>
+ <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"सध्या डायनॅमिक सिस्टीम रन करत आहे. मूळ Android आवृत्ती वापरण्यासाठी रीस्टार्ट करा."</string>
<string name="notification_action_cancel" msgid="5929299408545961077">"रद्द करा"</string>
<string name="notification_action_discard" msgid="1817481003134947493">"काढून टाका"</string>
<string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"रीस्टार्ट करा"</string>
<string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"रीस्टार्ट करा"</string>
- <string name="toast_dynsystem_discarded" msgid="1733249860276017050">"डायनॅमिक सिस्टम काढून टाकली"</string>
- <string name="toast_failed_to_reboot_to_dynsystem" msgid="6336737274625452067">"डायनॅमिक सिस्टम रीस्टार्ट किंवा लोड करू शकत नाही"</string>
- <string name="toast_failed_to_disable_dynsystem" msgid="3285742944977744413">"डायनॅमिक सिस्टम बंद करता आली नाही"</string>
+ <string name="toast_dynsystem_discarded" msgid="1733249860276017050">"डायनॅमिक सिस्टीम काढून टाकली"</string>
+ <string name="toast_failed_to_reboot_to_dynsystem" msgid="6336737274625452067">"डायनॅमिक सिस्टीम रीस्टार्ट किंवा लोड करू शकत नाही"</string>
+ <string name="toast_failed_to_disable_dynsystem" msgid="3285742944977744413">"डायनॅमिक सिस्टीम बंद करता आली नाही"</string>
</resources>
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt b/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt
index bb3a04df6f36..705d9e1cf442 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt
@@ -127,7 +127,7 @@ class UniverseProgressNotifier(val context: Context, val universe: Universe) {
val eta = if (speed > 0) "%1.0fs".format(distToTarget / speed) else "???"
builder.setContentTitle("headed to: ${target.name}")
builder.setContentText(
- "autopilot is ${autopilot.strategy.toLowerCase()}" +
+ "autopilot is ${autopilot.strategy.lowercase()}" +
"\ndist: ${distToTarget}u // eta: $eta"
)
// fun fact: ProgressStyle was originally EnRouteStyle
diff --git a/packages/ExternalStorageProvider/res/values-fa/strings.xml b/packages/ExternalStorageProvider/res/values-fa/strings.xml
index 9eabfd027da2..28781f123ada 100644
--- a/packages/ExternalStorageProvider/res/values-fa/strings.xml
+++ b/packages/ExternalStorageProvider/res/values-fa/strings.xml
@@ -18,6 +18,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="748293919008814871">"حافظه خارجی"</string>
<string name="storage_description" msgid="9176081505553938524">"فضای ذخیره‌سازی محلی"</string>
- <string name="root_internal_storage" msgid="4980477711224234931">"حافظهٔ داخلی"</string>
+ <string name="root_internal_storage" msgid="4980477711224234931">"فضای ذخیره‌سازی داخلی"</string>
<string name="root_documents" msgid="5695037589229175941">"اسناد"</string>
</resources>
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 50331014f926..716845c5b985 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -23,6 +23,28 @@
]
},
{
+ "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJMultiUsersTestCases",
"options":[
{
@@ -120,6 +142,28 @@
]
},
{
+ "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJMultiUsersTestCases",
"options":[
{
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index 0bc4be3e53d6..f7998309f2c1 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/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="app_name" msgid="7488448184431507488">"प्याकेज स्थापनाकर्ता"</string>
- <string name="install" msgid="711829760615509273">"स्थापना गर्नु…"</string>
+ <string name="install" msgid="711829760615509273">"इन्स्टल"</string>
<string name="update" msgid="3932142540719227615">"अपडेट गर्नुहोस्"</string>
<string name="done" msgid="6632441120016885253">"सम्पन्न भयो"</string>
<string name="cancel" msgid="1018267193425558088">"रद्द गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
index 472ffa9289a7..6dec2f999630 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.datastore
import android.content.SharedPreferences
+import android.util.Log
/** Interface of key-value store. */
interface KeyValueStore : KeyedObservable<String> {
@@ -80,6 +81,27 @@ interface KeyValueStore : KeyedObservable<String> {
fun setString(key: String, value: String?) = setValue(key, String::class.javaObjectType, value)
}
+/** Delegation of [KeyValueStore]. */
+interface KeyValueStoreDelegate : KeyValueStore, KeyedObservableDelegate<String> {
+
+ /** [KeyValueStore] to delegate. */
+ val keyValueStoreDelegate: KeyValueStore
+
+ override val keyedObservableDelegate
+ get() = keyValueStoreDelegate
+
+ override fun contains(key: String) = keyValueStoreDelegate.contains(key)
+
+ override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) =
+ keyValueStoreDelegate.getDefaultValue(key, valueType)
+
+ override fun <T : Any> getValue(key: String, valueType: Class<T>) =
+ keyValueStoreDelegate.getValue(key, valueType) ?: getDefaultValue(key, valueType)
+
+ override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) =
+ keyValueStoreDelegate.setValue(key, valueType, value)
+}
+
/** [SharedPreferences] based [KeyValueStore]. */
interface SharedPreferencesKeyValueStore : KeyValueStore {
@@ -103,11 +125,11 @@ interface SharedPreferencesKeyValueStore : KeyValueStore {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+ val edit = sharedPreferences.edit()
if (value == null) {
- sharedPreferences.edit().remove(key).apply()
+ edit.remove(key).apply()
return
}
- val edit = sharedPreferences.edit()
when (valueType) {
Boolean::class.javaObjectType -> edit.putBoolean(key, value as Boolean)
Float::class.javaObjectType -> edit.putFloat(key, value as Float)
@@ -115,7 +137,7 @@ interface SharedPreferencesKeyValueStore : KeyValueStore {
Long::class.javaObjectType -> edit.putLong(key, value as Long)
String::class.javaObjectType -> edit.putString(key, value as String)
Set::class.javaObjectType -> edit.putStringSet(key, value as Set<String>)
- else -> {}
+ else -> Log.e(LOG_TAG, "Unsupported $valueType for $key: $value")
}
edit.apply()
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index 07b1c9e3385e..ff58bf7b8728 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -116,8 +116,28 @@ interface KeyedObservable<K> {
}
/** Delegation of [KeyedObservable]. */
-open class KeyedObservableDelegate<K>(delegate: KeyedObservable<K>) :
- KeyedObservable<K> by delegate
+interface KeyedObservableDelegate<K> : KeyedObservable<K> {
+
+ /** [KeyedObservable] to delegate. */
+ val keyedObservableDelegate: KeyedObservable<K>
+
+ override fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean =
+ keyedObservableDelegate.addObserver(observer, executor)
+
+ override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean =
+ keyedObservableDelegate.addObserver(key, observer, executor)
+
+ override fun removeObserver(observer: KeyedObserver<K?>): Boolean =
+ keyedObservableDelegate.removeObserver(observer)
+
+ override fun removeObserver(key: K, observer: KeyedObserver<K>): Boolean =
+ keyedObservableDelegate.removeObserver(key, observer)
+
+ override fun notifyChange(reason: Int): Unit = keyedObservableDelegate.notifyChange(reason)
+
+ override fun notifyChange(key: K, reason: Int): Unit =
+ keyedObservableDelegate.notifyChange(key, reason)
+}
/** A thread safe implementation of [KeyedObservable]. */
open class KeyedDataObservable<K> : KeyedObservable<K> {
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt
new file mode 100644
index 000000000000..fdde3d3f5669
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate
+
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK
+
+/**
+ * Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting.
+ *
+ * It provides methods to register/unregister listeners for setting changes, update the setting for
+ * specific device states, retrieve the setting value, and check if rotation is locked for specific
+ * or all device states.
+ */
+interface DeviceStateAutoRotateSettingManager {
+ // TODO: b/397928958 - Rename all terms from rotationLock to autoRotate in all apis.
+
+ /** Listener for changes in device-state based auto rotate setting. */
+ interface DeviceStateAutoRotateSettingListener {
+ /** Called whenever the setting has changed. */
+ fun onSettingsChanged()
+ }
+
+ /** Register listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */
+ fun registerListener(settingListener: DeviceStateAutoRotateSettingListener)
+
+ /** Unregister listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */
+ fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener)
+
+ /**
+ * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] setting.
+ */
+ fun updateSetting(deviceState: Int, autoRotate: Boolean)
+
+ /** Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. */
+ fun getRotationLockSetting(deviceState: Int): Int
+
+ /** Returns true if auto-rotate setting is OFF for [deviceState]. */
+ fun isRotationLocked(deviceState: Int): Boolean
+
+ /** Returns true if the auto-rotate setting value for all device states is OFF. */
+ fun isRotationLockedForAllStates(): Boolean
+
+ /** Returns a list of device states and their respective auto rotate setting availability. */
+ fun getSettableDeviceStates(): List<SettableDeviceState>
+}
+
+/** Represents a device state and whether it has an auto-rotation setting. */
+data class SettableDeviceState(
+ /** Returns the device state associated with this object. */
+ val deviceState: Int,
+ /** Returns whether there is an auto-rotation setting for this device state. */
+ val isSettable: Boolean
+)
+
+
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt
new file mode 100644
index 000000000000..0b6c6e238956
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.devicestate
+
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
+import android.util.Log
+import android.util.SparseIntArray
+import com.android.internal.R
+import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
+import com.android.window.flags.Flags
+import java.util.concurrent.Executor
+
+/**
+ * Implementation of [DeviceStateAutoRotateSettingManager]. This implementation is a part of
+ * refactoring, it should be used when [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR]
+ * is enabled.
+ */
+class DeviceStateAutoRotateSettingManagerImpl(
+ context: Context,
+ backgroundExecutor: Executor,
+ private val secureSettings: SecureSettings,
+ private val mainHandler: Handler,
+ private val posturesHelper: PosturesHelper,
+) : DeviceStateAutoRotateSettingManager {
+ // TODO: b/397928958 rename the fields and apis from rotationLock to autoRotate.
+
+ private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> =
+ mutableListOf()
+ private val fallbackPostureMap = SparseIntArray()
+ private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf()
+
+ private val autoRotateSettingValue: String
+ get() = secureSettings.getStringForUser(DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)
+
+ init {
+ loadAutoRotateDeviceStates(context)
+ val contentObserver =
+ object : ContentObserver(mainHandler) {
+ override fun onChange(selfChange: Boolean) = notifyListeners()
+ }
+ backgroundExecutor.execute {
+ secureSettings.registerContentObserver(
+ DEVICE_STATE_ROTATION_LOCK, false, contentObserver, UserHandle.USER_CURRENT
+ )
+ }
+ }
+
+ override fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) {
+ settingListeners.add(settingListener)
+ }
+
+ override fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) {
+ if (!settingListeners.remove(settingListener)) {
+ Log.w(TAG, "Attempting to unregister a listener hadn't been registered")
+ }
+ }
+
+ override fun getRotationLockSetting(deviceState: Int): Int {
+ val devicePosture = posturesHelper.deviceStateToPosture(deviceState)
+ val serializedSetting = autoRotateSettingValue
+ val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting)
+
+ // If the setting is ignored for this posture, check the fallback posture.
+ if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
+ val fallbackPosture =
+ fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED)
+ return extractSettingForDevicePosture(fallbackPosture, serializedSetting)
+ }
+
+ return autoRotateSetting
+ }
+
+ override fun isRotationLocked(deviceState: Int) =
+ getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED
+
+ override fun isRotationLockedForAllStates(): Boolean =
+ convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) ->
+ value == DEVICE_STATE_ROTATION_LOCK_LOCKED
+ }
+
+ override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState
+
+ override fun updateSetting(deviceState: Int, autoRotate: Boolean) {
+ // TODO: b/350946537 - Create IPC to update the setting, and call it here.
+ throw UnsupportedOperationException("API updateSetting is not implemented yet")
+ }
+
+ private fun notifyListeners() =
+ settingListeners.forEach { listener -> listener.onSettingsChanged() }
+
+ private fun loadAutoRotateDeviceStates(context: Context) {
+ val perDeviceStateAutoRotateDefaults =
+ context.resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
+ for (entry in perDeviceStateAutoRotateDefaults) {
+ entry.parsePostureEntry()?.let { (posture, autoRotate, fallbackPosture) ->
+ if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) {
+ fallbackPostureMap.put(posture, fallbackPosture)
+ }
+ settableDeviceState.add(
+ SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED)
+ )
+ }
+ }
+ }
+
+ private fun convertSerializedSettingToMap(serializedSetting: String): Map<Int, Int> {
+ if (serializedSetting.isEmpty()) return emptyMap()
+ return try {
+ serializedSetting
+ .split(SEPARATOR_REGEX)
+ .hasEvenSize()
+ .chunked(2)
+ .mapNotNull(::parsePostureSettingPair)
+ .toMap()
+ } catch (e: Exception) {
+ Log.w(
+ TAG,
+ "Invalid format in serializedSetting=$serializedSetting: ${e.message}"
+ )
+ return emptyMap()
+ }
+ }
+
+ private fun List<String>.hasEvenSize(): List<String> {
+ if (this.size % 2 != 0) {
+ throw IllegalStateException("Odd number of elements in the list")
+ }
+ return this
+ }
+
+ private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int>? {
+ return settingPair.let { (keyStr, valueStr) ->
+ val key = keyStr.toIntOrNull()
+ val value = valueStr.toIntOrNull()
+ if (key != null && value != null && value in 0..2) {
+ key to value
+ } else {
+ Log.w(TAG, "Invalid key or value in pair: $keyStr, $valueStr")
+ null // Invalid pair, skip it
+ }
+ }
+ }
+
+ private fun extractSettingForDevicePosture(
+ devicePosture: Int,
+ serializedSetting: String
+ ): Int =
+ convertSerializedSettingToMap(serializedSetting)[devicePosture]
+ ?: DEVICE_STATE_ROTATION_LOCK_IGNORED
+
+ private fun String.parsePostureEntry(): Triple<Int, Int, Int?>? {
+ val values = split(SEPARATOR_REGEX)
+ if (values.size !in 2..3) { // It should contain 2 or 3 values.
+ Log.w(TAG, "Invalid number of values in entry: '$this'")
+ return null
+ }
+ return try {
+ val posture = values[0].toInt()
+ val rotationLockSetting = values[1].toInt()
+ val fallbackPosture = if (values.size == 3) values[2].toIntOrNull() else null
+ Triple(posture, rotationLockSetting, fallbackPosture)
+ } catch (e: NumberFormatException) {
+ Log.w(TAG, "Invalid number format in '$this': ${e.message}")
+ null
+ }
+ }
+
+ companion object {
+ private const val TAG = "DeviceStateAutoRotate"
+ private const val SEPARATOR_REGEX = ":"
+ }
+}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt
new file mode 100644
index 000000000000..4d1d29242832
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingUtils.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("DeviceStateAutoRotateSettingUtils")
+
+package com.android.settingslib.devicestate
+
+import android.content.Context
+import com.android.internal.R
+
+/** Returns true if device-state based rotation lock settings are enabled. */
+object DeviceStateAutoRotateSettingUtils {
+ @JvmStatic
+ fun isDeviceStateRotationLockEnabled(context: Context) =
+ context.resources
+ .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
+ .isNotEmpty()
+}
+
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index 635f6905e4f0..deeba574f2ad 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
@@ -20,6 +20,8 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
+import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener;
+
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,7 +45,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
/**
@@ -58,7 +59,7 @@ public final class DeviceStateRotationLockSettingsManager {
private static DeviceStateRotationLockSettingsManager sSingleton;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
- private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
+ private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>();
private final SecureSettings mSecureSettings;
private final PosturesHelper mPosturesHelper;
private String[] mPostureRotationLockDefaults;
@@ -127,20 +128,20 @@ public final class DeviceStateRotationLockSettingsManager {
}
/**
- * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings
+ * Registers a {@link DeviceStateAutoRotateSettingListener} to be notified when the settings
* change. Can be called multiple times with different listeners.
*/
- public void registerListener(DeviceStateRotationLockSettingsListener runnable) {
+ public void registerListener(DeviceStateAutoRotateSettingListener runnable) {
mListeners.add(runnable);
}
/**
- * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance
+ * Unregisters a {@link DeviceStateAutoRotateSettingListener}. No-op if the given instance
* was never registered.
*/
public void unregisterListener(
- DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) {
- if (!mListeners.remove(deviceStateRotationLockSettingsListener)) {
+ DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) {
+ if (!mListeners.remove(deviceStateAutoRotateSettingListener)) {
Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
}
}
@@ -379,56 +380,8 @@ public final class DeviceStateRotationLockSettingsManager {
}
private void notifyListeners() {
- for (DeviceStateRotationLockSettingsListener r : mListeners) {
+ for (DeviceStateAutoRotateSettingListener r : mListeners) {
r.onSettingsChanged();
}
}
-
- /** Listener for changes in device-state based rotation lock settings */
- public interface DeviceStateRotationLockSettingsListener {
- /** Called whenever the settings have changed. */
- void onSettingsChanged();
- }
-
- /** Represents a device state and whether it has an auto-rotation setting. */
- public static class SettableDeviceState {
- private final int mDeviceState;
- private final boolean mIsSettable;
-
- SettableDeviceState(int deviceState, boolean isSettable) {
- mDeviceState = deviceState;
- mIsSettable = isSettable;
- }
-
- /** Returns the device state associated with this object. */
- public int getDeviceState() {
- return mDeviceState;
- }
-
- /** Returns whether there is an auto-rotation setting for this device state. */
- public boolean isSettable() {
- return mIsSettable;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SettableDeviceState)) return false;
- SettableDeviceState that = (SettableDeviceState) o;
- return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mDeviceState, mIsSettable);
- }
-
- @Override
- public String toString() {
- return "SettableDeviceState{"
- + "mDeviceState=" + mDeviceState
- + ", mIsSettable=" + mIsSettable
- + '}';
- }
- }
}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java
index 10528739b2b0..ea40e148aed6 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java
@@ -19,7 +19,7 @@ package com.android.settingslib.devicestate;
import android.database.ContentObserver;
/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */
-interface SecureSettings {
+public interface SecureSettings {
void putStringForUser(String name, String value, int userHandle);
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 13541b1ebc9a..009d265833b4 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -58,6 +58,7 @@ import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIVITY
import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY
+import com.android.settingslib.metadata.getPreferenceIcon
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/strings.xml b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml
index 3a8aaf8b5092..03da0dc41d47 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/strings.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml
@@ -20,8 +20,6 @@
<string name="settingslib_action_label_resume">resume</string>
<!-- Label for an accessibility action that stops an animation [CHAR LIMIT=30] -->
<string name="settingslib_action_label_pause">pause</string>
- <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] -->
- <string name="settingslib_state_animation_playing">Animation playing</string>
- <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] -->
- <string name="settingslib_state_animation_paused">Animation paused</string>
+ <!-- Default content description attached to the illustration if there is no content description. [CHAR LIMIT=NONE] -->
+ <string name="settingslib_illustration_content_description">Animation</string>
</resources>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index e818a603c5b4..777607010b3a 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -138,8 +138,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
ImageView backgroundViewTablet =
(ImageView) holder.findViewById(R.id.background_view_tablet);
- backgroundView.setVisibility(mIsTablet ? View.GONE : View.VISIBLE);
- backgroundViewTablet.setVisibility(mIsTablet ? View.VISIBLE : View.GONE);
+ if (backgroundView != null) {
+ backgroundView.setVisibility(mIsTablet ? View.GONE : View.VISIBLE);
+ }
+ if (backgroundViewTablet != null) {
+ backgroundViewTablet.setVisibility(mIsTablet ? View.VISIBLE : View.GONE);
+ }
if (mIsTablet) {
backgroundView = backgroundViewTablet;
}
@@ -183,7 +187,9 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
if (mLottieDynamicColor) {
LottieColorUtils.applyDynamicColors(getContext(), illustrationView);
}
- LottieColorUtils.applyMaterialColor(getContext(), illustrationView);
+ if (SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ LottieColorUtils.applyMaterialColor(getContext(), illustrationView);
+ }
if (mOnBindListener != null) {
mOnBindListener.onBind(illustrationView);
@@ -443,6 +449,10 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
illustrationView.setMaxWidth((int) (restrictedMaxHeight * aspectRatio));
}
+ public boolean isAnimatable() {
+ return mIsAnimatable;
+ }
+
private void startAnimation(Drawable drawable) {
if (!(drawable instanceof Animatable)) {
return;
@@ -464,6 +474,10 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
if (mIsAnimatable) {
// TODO(b/397340540): list out pages having illustration without a content description.
if (TextUtils.isEmpty(mContentDescription)) {
+ // Default content description will be attached if there's no content description.
+ illustrationView.setContentDescription(
+ getContext().getString(
+ R.string.settingslib_illustration_content_description));
Log.w(TAG, "Illustration should have a content description. preference key = "
+ getKey());
}
@@ -483,8 +497,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
}
private void updateAccessibilityAction(ViewGroup container) {
- // Setting the state of animation
- container.setStateDescription(getStateDescriptionForAnimation());
container.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -505,14 +517,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
}
}
- private String getStateDescriptionForAnimation() {
- if (mIsAnimationPaused) {
- return getContext().getString(R.string.settingslib_state_animation_paused);
- } else {
- return getContext().getString(R.string.settingslib_state_animation_playing);
- }
- }
-
private static void startLottieAnimationWith(LottieAnimationView illustrationView,
Uri imageUri) {
final InputStream inputStream =
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 4421424c0e39..e59cc81d3ba8 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -157,10 +157,6 @@ public class LottieColorUtils {
/** Applies material colors. */
public static void applyMaterialColor(@NonNull Context context,
@NonNull LottieAnimationView lottieAnimationView) {
- if (!SettingsThemeHelper.isExpressiveTheme(context)) {
- return;
- }
-
for (String key : MATERIAL_COLOR_MAP.keySet()) {
final int color = context.getColor(MATERIAL_COLOR_MAP.get(key));
lottieAnimationView.addValueCallback(
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
index 94c6924a02f2..7adbfbfb1ffd 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
@@ -19,14 +19,14 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:importantForAccessibility="no">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<com.android.settingslib.widget.MainSwitchBar
android:id="@+id/settingslib_main_switch_bar"
android:visibility="gone"
android:layout_height="wrap_content"
- android:layout_width="match_parent" />
+ android:layout_width="match_parent"
+ android:importantForAccessibility="no" />
</FrameLayout>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
index bef6e352d854..c5dd24cc4d2e 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
@@ -17,14 +17,14 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:importantForAccessibility="no">
+ android:layout_width="match_parent">
<com.android.settingslib.widget.MainSwitchBar
android:id="@+id/settingslib_main_switch_bar"
android:visibility="gone"
android:layout_height="wrap_content"
- android:layout_width="match_parent" />
+ android:layout_width="match_parent"
+ android:importantForAccessibility="no" />
</FrameLayout>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 7bd4b3f771ab..ddd9d2acdab3 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -112,26 +112,6 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen
if (mSwitch.getVisibility() == VISIBLE) {
mSwitch.setOnCheckedChangeListener(this);
}
-
- setChecked(mSwitch.isChecked());
-
- if (attrs != null) {
- final TypedArray a = context.obtainStyledAttributes(attrs,
- androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
- 0 /*defStyleRes*/);
- final CharSequence title = a.getText(
- androidx.preference.R.styleable.Preference_android_title);
- setTitle(title);
- //TODO(b/369470034): update to next version
- if (isExpressive && Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) {
- CharSequence summary = a.getText(
- androidx.preference.R.styleable.Preference_android_summary);
- setSummary(summary);
- }
- a.recycle();
- }
-
- setBackground(mSwitch.isChecked());
}
@Override
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index d883fb0594e6..5170581aa382 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -80,7 +80,14 @@ public class MainSwitchPreference extends TwoStatePreference implements OnChecke
mainSwitchBar.setIconSpaceReserved(isIconSpaceReserved());
// To support onPreferenceChange callback, it needs to call callChangeListener() when
// MainSwitchBar is clicked.
- mainSwitchBar.setOnClickListener(view -> callChangeListener(isChecked()));
+ mainSwitchBar.setOnClickListener(view -> {
+ boolean isChecked = isChecked();
+ if (!callChangeListener(isChecked)) {
+ // Change checked state back if listener doesn't like it.
+ // Note that CompoundButton maintains internal state to avoid infinite recursion.
+ mainSwitchBar.setChecked(!isChecked);
+ }
+ });
// Remove all listeners to 1. avoid triggering listener when update UI 2. prevent potential
// listener leaking when the view holder is reused by RecyclerView
@@ -88,7 +95,11 @@ public class MainSwitchPreference extends TwoStatePreference implements OnChecke
mainSwitchBar.setChecked(isChecked());
mainSwitchBar.addOnSwitchChangeListener(this);
- mainSwitchBar.show();
+ if (isVisible()) {
+ mainSwitchBar.show();
+ } else {
+ mainSwitchBar.hide();
+ }
}
@Override
@@ -101,7 +112,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnChecke
/**
* Adds a listener for switch changes
+ *
+ * @deprecated Use {@link #setOnPreferenceChangeListener(OnPreferenceChangeListener)}
*/
+ @Deprecated
public void addOnSwitchChangeListener(OnCheckedChangeListener listener) {
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
@@ -110,7 +124,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnChecke
/**
* Remove a listener for switch changes
+ *
+ * @deprecated Use {@link #setOnPreferenceChangeListener(OnPreferenceChangeListener)}
*/
+ @Deprecated
public void removeOnSwitchChangeListener(OnCheckedChangeListener listener) {
mSwitchChangeListeners.remove(listener);
}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
index 7f2a61081fbb..fcca82330d10 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -137,44 +137,6 @@ interface PreferenceMetadata {
/** Returns preference intent. */
fun intent(context: Context): Intent? = null
-
- /**
- * Returns the preference title.
- *
- * Implement [PreferenceTitleProvider] interface if title content is generated dynamically.
- */
- fun getPreferenceTitle(context: Context): CharSequence? =
- when {
- title != 0 -> context.getText(title)
- this is PreferenceTitleProvider -> getTitle(context)
- else -> null
- }
-
- /**
- * Returns the preference summary.
- *
- * Implement [PreferenceSummaryProvider] interface if summary content is generated dynamically
- * (e.g. summary is provided per preference value).
- */
- fun getPreferenceSummary(context: Context): CharSequence? =
- when {
- summary != 0 -> context.getText(summary)
- this is PreferenceSummaryProvider -> getSummary(context)
- else -> null
- }
-
- /**
- * Returns the preference icon.
- *
- * Implement [PreferenceIconProvider] interface if icon is provided dynamically (e.g. icon is
- * provided based on flag value).
- */
- fun getPreferenceIcon(context: Context): Int =
- when {
- icon != 0 -> icon
- this is PreferenceIconProvider -> getIcon(context)
- else -> 0
- }
}
/** Metadata of preference group. */
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt
new file mode 100644
index 000000000000..6d580fb47160
--- /dev/null
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Utils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.metadata
+
+import android.content.Context
+
+/** Returns the preference screen title. */
+fun PreferenceScreenMetadata.getPreferenceScreenTitle(context: Context): CharSequence? =
+ when {
+ screenTitle != 0 -> context.getString(screenTitle)
+ else -> getScreenTitle(context) ?: (this as? PreferenceTitleProvider)?.getTitle(context)
+ }
+
+/** Returns the preference title. */
+fun PreferenceMetadata.getPreferenceTitle(context: Context): CharSequence? =
+ when {
+ title != 0 -> context.getText(title)
+ this is PreferenceTitleProvider -> getTitle(context)
+ else -> null
+ }
+
+/** Returns the preference summary. */
+fun PreferenceMetadata.getPreferenceSummary(context: Context): CharSequence? =
+ when {
+ summary != 0 -> context.getText(summary)
+ this is PreferenceSummaryProvider -> getSummary(context)
+ else -> null
+ }
+
+/** Returns the preference icon. */
+fun PreferenceMetadata.getPreferenceIcon(context: Context): Int =
+ when {
+ icon != 0 -> icon
+ this is PreferenceIconProvider -> getIcon(context)
+ else -> 0
+ }
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 59141c99e587..8896af47a1c2 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -25,10 +25,16 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SeekBarPreference
import com.android.settingslib.metadata.DiscreteIntValue
import com.android.settingslib.metadata.DiscreteValue
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
+import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
import com.android.settingslib.metadata.IntRangeValuePreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
+import com.android.settingslib.metadata.getPreferenceIcon
+import com.android.settingslib.metadata.getPreferenceScreenTitle
+import com.android.settingslib.metadata.getPreferenceSummary
+import com.android.settingslib.metadata.getPreferenceTitle
/** Binding of preference widget and preference metadata. */
interface PreferenceBinding {
@@ -72,9 +78,22 @@ interface PreferenceBinding {
preference.icon = null
}
val isPreferenceScreen = preference is PreferenceScreen
+ val screenMetadata = this as? PreferenceScreenMetadata
+ // extras
preference.peekExtras()?.clear()
extras(context)?.let { preference.extras.putAll(it) }
- preference.title = getPreferenceTitle(context)
+ if (!isPreferenceScreen && screenMetadata != null) {
+ val extras = preference.extras
+ // Pass the preference key to fragment, so that the fragment could find associated
+ // preference screen registered in PreferenceScreenRegistry
+ extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key)
+ screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) }
+ }
+ preference.title =
+ when {
+ isPreferenceScreen -> screenMetadata?.getPreferenceScreenTitle(context)
+ else -> getPreferenceTitle(context)
+ }
if (!isPreferenceScreen) {
preference.summary = getPreferenceSummary(context)
}
@@ -82,12 +101,12 @@ interface PreferenceBinding {
preference.isVisible =
(this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false
preference.isPersistent = isPersistent(context)
- // PreferenceRegistry will notify dependency change, so we do not need to set
+ // PreferenceScreenBindingHelper will notify dependency change, so we do not need to set
// dependency here. This simplifies dependency management and avoid the
// IllegalStateException when call Preference.setDependency
preference.dependency = null
if (!isPreferenceScreen) { // avoid recursive loop when build graph
- preference.fragment = (this as? PreferenceScreenCreator)?.fragmentClass()?.name
+ preference.fragment = screenMetadata?.fragmentClass()?.name
preference.intent = intent(context)
}
if (preference is DialogPreference) {
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 6287fda86a73..33b614e19bbe 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -60,7 +60,6 @@ open class DefaultPreferenceBindingFactory : PreferenceBindingFactory {
?: when (metadata) {
is SwitchPreference -> SwitchPreferenceBinding.INSTANCE
is PreferenceCategory -> PreferenceCategoryBinding.INSTANCE
- is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE
is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE
else -> DefaultPreferenceBinding
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index 44c93c77e33b..71c46fa76aed 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -19,45 +19,11 @@ package com.android.settingslib.preference
import android.content.Context
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
-import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.TwoStatePreference
-import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
-import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
import com.android.settingslib.metadata.PreferenceMetadata
-import com.android.settingslib.metadata.PreferenceScreenMetadata
-import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.widget.MainSwitchPreference
-/** Binding of preference group associated with [PreferenceCategory]. */
-interface PreferenceScreenBinding : PreferenceBinding {
-
- override fun bind(preference: Preference, metadata: PreferenceMetadata) {
- super.bind(preference, metadata)
- val context = preference.context
- val screenMetadata = metadata as PreferenceScreenMetadata
- val extras = preference.extras
- // Pass the preference key to fragment, so that the fragment could find associated
- // preference screen registered in PreferenceScreenRegistry
- extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key)
- screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) }
- if (preference is PreferenceScreen) {
- val screenTitle = screenMetadata.screenTitle
- preference.title =
- if (screenTitle != 0) {
- context.getString(screenTitle)
- } else {
- screenMetadata.getScreenTitle(context)
- ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context)
- }
- }
- }
-
- companion object {
- @JvmStatic val INSTANCE = object : PreferenceScreenBinding {}
- }
-}
-
/** Binding of preference category associated with [PreferenceCategory]. */
interface PreferenceCategoryBinding : PreferenceBinding {
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
index b41ec957f12d..9d3092d39d03 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Geaktiveer deur administrateur"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Gedeaktiveer deur administrateur"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Geaktiveer deur Gevorderde Beskerming"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Gedeaktiveer deur Gevorderde Beskerming"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
index 8e9488453032..9617acaa0c14 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"በአስተዳዳሪ ነቅቷል"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"በአስተዳዳሪ ተሰናክሏል"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"በላቀ ጥበቃ የነቃ"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"በላቀ ጥበቃ የተሰናከለ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
index 8b2ccdfbb0e8..581b91458c1c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"يفعِّل المشرف هذا الإعداد."</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"أوقف المشرف هذا الإعداد"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"تم التفعيل من خلال ميزة \"الحماية المتقدّمة\""</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"تم الإيقاف من خلال ميزة \"الحماية المتقدّمة\""</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
index 03e9e828534a..5824abdf921c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"প্ৰশাসকে সক্ষম কৰিছে"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"প্ৰশাসকে অক্ষম কৰিছে"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"সুৰক্ষা সম্পৰ্কীয় উন্নত সুবিধাটোৱে সক্ষম কৰিছে"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"সুৰক্ষা সম্পৰ্কীয় উন্নত সুবিধাটোৱে অক্ষম কৰিছে"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
index 98447166a156..f07e05403afb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Admin tərəfindən aktiv edildi"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Admin tərəfindən deaktiv edildi"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Qabaqcıl Qoruma tərəfindən aktiv edilib"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Qabaqcıl Qoruma tərəfindən deaktiv edilib"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
index c7b9be28fb1e..e09afbfbbd63 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administrator je omogućio"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administrator je onemogućio"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogućila je Napredna zaštita"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogućila je Napredna zaštita"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
index 92ed11157dfc..a64734bde002 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Уключана адміністратарам"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Адключана адміністратарам"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Уключана Палепшанай абаронай"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Адключана Палепшанай абаронай"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
index 57b50c5c580c..ccaa6563cbe9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Активирано от администратора"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Деактивирано от администратора"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Активирано от „Разширена защита“"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Деактивирано от „Разширена защита“"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
index 939ceb82ab40..0a48aa21a36b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"অ্যাডমিন চালু করেছেন"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"অ্যাডমিন বন্ধ করেছেন"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"উন্নত সুরক্ষা চালু করেছে"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"উন্নত সুরক্ষা বন্ধ করেছে"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
index 87cd3b8905c7..eebcebffe2a1 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Omogućio administrator"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Onemogućio administrator"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogućeno je Naprednom zaštitom"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogućeno je Naprednom zaštitom"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
index 34099b6f3f08..51e3fa9c8a41 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activat per l\'administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desactivat per l\'administrador"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activat per la Protecció avançada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Desactivat per la Protecció avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
index 82cd56f39059..a5db6099c8cb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Zapnuto administrátorem"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Vypnuto administrátorem"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivováno pokročilou ochranou"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktivováno pokročilou ochranou"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
index 7f7ae8b3d5e0..7f10edf158b6 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktiveret af administratoren"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Deaktiveret af administrator"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktiveret af Avanceret beskyttelse"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktiveret af Avanceret beskyttelse"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
index efaa50efacf4..604593bf0966 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Vom Administrator aktiviert"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Vom Administrator deaktiviert"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Vom erweiterten Sicherheitsprogramm aktiviert"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Vom erweiterten Sicherheitsprogramm deaktiviert"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
index ddde3ece472a..79b401607a94 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ενεργοποιήθηκε από τον διαχειριστή"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Απενεργοποιήθηκε από τον διαχειριστή"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Ενεργοποιήθηκε από την Ενισχυμένη προστασία"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Απενεργοποιήθηκε από την Ενισχυμένη προστασία"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
index 6a07741d8e4c..14b92720487e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
index 6a07741d8e4c..14b92720487e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
index 6a07741d8e4c..14b92720487e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
index 6a07741d8e4c..14b92720487e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
index 8dc15f77f6e0..616b568d58fe 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"El administrador habilitó la opción"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"El administrador inhabilitó la opción"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Habilitado por la Protección avanzada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Inhabilitado por la Protección avanzada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
index 7c9864d7e81e..351f16cb1a24 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Habilitado por el administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Inhabilitado por el administrador"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Habilitado por Protección Avanzada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Inhabilitado por Protección Avanzada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
index 5939b584a0f2..c59d6459789e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administraatori lubatud"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administraatori keelatud"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Lubatud täiustatud kaitsega"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Keelatud täiustatud kaitsega"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
index 27bef6e92073..2a881247c3af 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administratzaileak gaitu egin du"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administratzaileak desgaitu du"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Babes aurreratua programak gaitu du"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Babes aurreratua programak desgaitu du"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
index 8fb2646821dc..9c39f98aab17 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"توسط سرپرست فعال شده"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"توسط سرپرست غیرفعال شده"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"فعال‌شده با «محافظت پیشرفته»"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"غیرفعال‌شده با «محافظت پیشرفته»"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
index cd7cbd3d83ca..41fef5af7033 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Järjestelmänvalvojan sallima"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Järjestelmänvalvojan estämä"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Lisäsuojaus on ottanut asetuksen käyttöön"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Lisäsuojaus on poistanut asetuksen käytöstä"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
index da74cf6a3ab9..9ff117427555 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activé par l\'administrateur"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Désactivé par l\'administrateur"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activée par la protection avancée"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Désactivée par la protection avancée"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
index 2215af24664c..9ff117427555 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activé par l\'administrateur"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Désactivé par l\'administrateur"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activé par la Protection Avancée"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Désactivé par la Protection Avancée"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
index 92f33bbf2533..dbf8f7d83b6c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Opción activada polo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Opción desactivada polo administrador"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Opción activada por Protección avanzada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Opción desactivada por Protección avanzada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
index 026bdb1127d3..4fc4ab48059d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ઍડમિને બંધ કરેલું"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"અદ્યતન સુરક્ષા દ્વારા ચાલુ કરવામાં આવી છે"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"અદ્યતન સુરક્ષા દ્વારા બંધ કરવામાં આવી છે"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
index 8fc8fd04ad93..6de84388bbe4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"एडमिन की ओर से चालू किया गया"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"एडमिन ने यह सुविधा बंद की हुई है"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"\'ऐडवांस सुरक्षा\' सेटिंग ने चालू किया है"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"\'ऐडवांस सुरक्षा\' सेटिंग ने बंद किया है"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
index 40605a3b83fb..eebcebffe2a1 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Omogućio administrator"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Onemogućio administrator"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogućila je napredna zaštita"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogućila je napredna zaštita"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
index 59135a42fd2a..ecfa2c794e28 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"A rendszergazda bekapcsolta"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"A rendszergazda letiltotta"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Engedélyezte a Speciális védelem"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Letiltotta a Speciális védelem"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
index 0221f9333249..23a2a6b125eb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Միացված է ադմինիստրատորի կողմից"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Անջատվել է ադմինիստրատորի կողմից"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Միացվել է Լրացուցիչ պաշտպանության կողմից"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Անջատվել է Լրացուցիչ պաշտպանության կողմից"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
index 6beb4c9b2c26..ae8ec8251b7d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Diaktifkan oleh admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Dinonaktifkan oleh admin"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Diaktifkan oleh Perlindungan Lanjutan"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Dinonaktifkan oleh Perlindungan Lanjutan"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
index feb325bf2210..55380b333edd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Gert virkt af kerfisstjóra"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Gert óvirkt af kerfisstjóra"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Virkjað af ítarlegri vernd"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Gert óvirkt af ítarlegri vernd"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
index 616392026b55..bddf43ce6917 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Attivata dalla protezione avanzata"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disattivata dalla protezione avanzata"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
index c342041f6c6e..007de061fbe9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"מופעל על ידי מנהל המכשיר"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"האפשרות הושבתה על ידי האדמין"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ההעדפה הופעלה על ידי ההגנה המתקדמת"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ההעדפה הושבתה על ידי ההגנה המתקדמת"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
index bd386f5858a9..490efd099569 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"管理者によって有効にされています"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"管理者により無効にされています"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"高度な保護機能により有効になっています"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"高度な保護機能により無効になっています"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
index a6fde9022a21..5c394b832af9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ჩართულია ადმინისტრატორის მიერ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"გათიშულია ადმინისტრატორის მიერ"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ჩართულია დამატებითი დაცვის საშუალებით"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"გათიშულია დამატებითი დაცვის საშუალებით"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
index ed0f95c200f6..eff7e44c6a39 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Әкімші қосқан"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Әкімші өшірген"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Күшейтілген қорғаныс параметрі қосып қойды."</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Күшейтілген қорғаныс параметрі өшіріп тастады."</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
index f2f5ab8e1dbf..5a4f0748c796 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"បើកដោយ​អ្នកគ្រប់គ្រង"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"បានបិទដោយអ្នកគ្រប់គ្រង"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"បានបើកដោយការ​ការពារ​កម្រិតខ្ពស់"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"បានបិទដោយការ​ការពារ​កម្រិតខ្ពស់"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
index ebc41a52b4df..9b7a0d8b97bd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ನಿರ್ವಾಹಕರು ಸಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ನಿರ್ವಾಹಕರು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ಸುಧಾರಿತ ಸಂರಕ್ಷಣೆ ಮೂಲಕ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ಸುಧಾರಿತ ಸಂರಕ್ಷಣೆ ಮೂಲಕ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
index 552662b4be95..d4f134cf3adb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"관리자가 사용 설정함"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"관리자가 사용 중지함"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"고급 보호 기능으로 사용 설정됨"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"고급 보호 기능으로 사용 중지됨"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
index 375ea19ff66e..a934b51f6a98 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Администратор иштетип койгон"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Администратор өчүрүп койгон"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Өркүндөтүлгөн коргоо тарабынан иштетилди"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Өркүндөтүлгөн коргоо тарабынан өчүрүлдү"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
index 4b311c0ce38d..c2d80f28130e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ເປີດນຳໃຊ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ຖືກຜູ້ເບິ່ງແຍງລະບົບປິດໄວ້"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ໄດ້ເປີດການນຳໃຊ້ໂດຍການປົກປ້ອງຂັ້ນສູງ"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ໄດ້ປິດການນຳໃຊ້ໂດຍການປົກປ້ອງຂັ້ນສູງ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
index cbbe92374854..2e96a0ad7dae 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Įgalino administratorius"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Išjungė administratorius"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Įgalino Papildoma apsauga"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Išjungė Papildoma apsauga"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
index a5189aa70ff8..1d2bcb0d484b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Iespējoja administrators"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Atspējoja administrators"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Iespējota iestatījuma “Papildu aizsardzība” dēļ"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Atspējota iestatījuma “Papildu aizsardzība” dēļ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
index 993b4aea8b13..1c8f1d1a0c43 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Овозможено од администраторот"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Оневозможено од администраторот"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Овозможено од „Напредна заштита“"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Оневозможено од „Напредна заштита“"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
index 9deeb6aa2cfd..c4ee22491659 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"അഡ്‌മിൻ പ്രവർത്തനക്ഷമമാക്കി"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"അഡ്‌മിൻ പ്രവർത്തനരഹിതമാക്കി"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"വിപുലമായ പരിരക്ഷ പ്രവർത്തനക്ഷമമാക്കി"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"വിപുലമായ പരിരക്ഷ പ്രവർത്തനരഹിതമാക്കി"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
index c9a91de26591..472c50ac36a3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Админ идэвхжүүлсэн"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Админ цуцалсан"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Дэвшилтэт хамгаалалтаар идэвхжүүлсэн"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Дэвшилтэт хамгаалалтаар идэвхгүй болгосон"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
index ede12424de04..d01bfc4dea81 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"अ‍ॅडमिनने सुरू केलेले"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"अ‍ॅडमिनने बंद केलेले"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"प्रगत संरक्षणाद्वारे सुरू केले आहे"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"प्रगत संरक्षणाद्वारे बंद केले आहे"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
index e8f710a7c909..618ea8c13c37 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Didayakan oleh pentadbir"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Dilumpuhkan oleh pentadbir"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Didayakan oleh Perlindungan Lanjutan"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Dilumpuhkan oleh Perlindungan Lanjutan"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
index 97be99f43291..e4626000476e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"စီမံခန့်ခွဲသူက ဖွင့်ထားသည်"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"အဆင့်မြင့်ကာကွယ်ရေးက ဖွင့်ထားသည်"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"အဆင့်မြင့်ကာကွယ်ရေးက ပိတ်ထားသည်"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
index 971d1ccd190a..509e70b31645 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktivert av administratoren"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Deaktivert av administratoren"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivert av Avansert beskyttelse"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktivert av Avansert beskyttelse"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
index 3d2a74e3b270..15bb85c7b174 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"प्रशासकद्वारा सक्षम पारिएको"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"एडमिनले अफ गरेको"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"सुरक्षासम्बन्धी उन्नत सुविधाले अन गरेको छ"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"सुरक्षासम्बन्धी उन्नत सुविधाले अफ गरेको छ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
index 9830363c7f65..a73deafbc70f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aangezet door beheerder"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Uitgezet door beheerder"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aangezet door Geavanceerde beveiliging"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Uitgezet door Geavanceerde beveiliging"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
index 2dab1592c296..4ce6460f8b89 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ଆଡମିନଙ୍କ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ଆଡମିନଙ୍କ ଦ୍ଵାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ଆଡଭାନ୍ସଡ ପ୍ରୋଟେକସନ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ଆଡଭାନ୍ସଡ ପ୍ରୋଟେକସନ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
index 12f296a46b2d..1a3a133e6c41 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ਅਡਵਾਂਸ ਸੁਰੱਖਿਆ ਵੱਲੋਂ ਚਾਲੂ ਕੀਤੀ ਗਈ"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ਅਡਵਾਂਸ ਸੁਰੱਖਿਆ ਵੱਲੋਂ ਬੰਦ ਕੀਤੀ ਗਈ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
index df7394766339..0523e2b23ed3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Włączone przez administratora"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Wyłączone przez administratora"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Włączone przez Ochronę zaawansowaną"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Wyłączone przez Ochronę zaawansowaną"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
index a705ba421910..908e2fbbff5b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Preferência ativada pela Proteção Avançada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Preferência desativada pela Proteção Avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
index b01118381478..908e2fbbff5b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Ativado pela Proteção avançada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Desativado pela Proteção avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
index a705ba421910..908e2fbbff5b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Preferência ativada pela Proteção Avançada"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Preferência desativada pela Proteção Avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
index 3eb347abbc1b..ad41605c636f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activat de administrator"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Dezactivat de administrator"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activată de Protecția avansată"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Dezactivată de Protecția avansată"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
index a004a1fe60da..59006449133d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Включено администратором"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Отключено администратором"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Включено Дополнительной защитой"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Отключено Дополнительной защитой"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
index addc8b3ae15c..de89710d7acd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"පරිපාලක විසින් සබල කර ඇත"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ඔබගේ පරිපාලක විසින් අබල කර ඇත"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"උසස් ආරක්ෂණය මගින් සබල කර ඇත"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"උසස් ආරක්ෂණය මගින් අබල කර ඇත"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
index 0277696560b9..b8bb91942799 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Povolené správcom"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Zakázané správcom"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivované rozšírenou ochranou"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktivované rozšírenou ochranou"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
index eb886bcf1232..1d8ee573e233 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Omogočil skrbnik"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Onemogočil skrbnik"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogočila dodatna zaščita"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogočila dodatna zaščita"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
index 2de5ffe47c1c..4ee40bf39b59 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktivizuar nga administratori"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Çaktivizuar nga administratori"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivizuar nga \"Mbrojtja e përparuar\""</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Çaktivizuar nga \"Mbrojtja e përparuar\""</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
index 94f52a0b95a2..9d006a7ef4ef 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Администратор је омогућио"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Администратор је онемогућио"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Омогућила је Напредна заштита"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Онемогућила је Напредна заштита"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
index b41c4d8a8275..faea0703468e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktiverad av administratör"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Inaktiverad av administratören"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktiverades av Avancerat skydd"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Inaktiverades av Avancerat skydd"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
index 4d2e0d994972..59f511aea6fc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Imewashwa na msimamizi"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Imezimwa na msimamizi"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Imewashwa kwa kutumia Ulinzi wa Hali ya Juu"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Imezimwa kwa kutumia Ulinzi wa Hali ya Juu"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
index 55b300657eaf..3ef5f77e96dc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"நிர்வாகி இயக்கியுள்ளார்"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"நிர்வாகி முடக்கியுள்ளார்"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"மேம்பட்ட பாதுகாப்பு அமைப்பால் இயக்கப்பட்டது"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"மேம்பட்ட பாதுகாப்பு அமைப்பால் முடக்கப்பட்டது"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
index fc6d00b22d34..8f17dc5ec1e8 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"అడ్మిన్ ఎనేబుల్ చేశారు"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"అడ్మిన్ డిజేబుల్ చేశారు"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"అడ్వాన్స్‌డ్ ప్రొటెక్షన్ ద్వారా ఎనేబుల్ చేయబడింది"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"అడ్వాన్స్‌డ్ ప్రొటెక్షన్ ద్వారా డిజేబుల్ చేయబడింది"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
index 51da8dec6443..80fd0c04cb2f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"เปิดใช้โดยผู้ดูแลระบบ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ปิดใช้โดยผู้ดูแลระบบ"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"เปิดใช้โดยการปกป้องขั้นสูง"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ปิดใช้โดยการปกป้องขั้นสูง"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
index 7fbf0e7ace73..a4a538dc84ba 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Na-enable ng admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Na-disable ng admin"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Na-enable ng Advanced na Proteksyon"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Na-disable ng Advanced na Proteksyon"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
index a529ca557ba1..ac5ed6a152b7 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Yönetici tarafından etkinleştirildi"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Yönetici tarafından devre dışı bırakıldı"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Gelişmiş Koruma tarafından etkinleştirildi"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Gelişmiş Koruma tarafından devre dışı bırakıldı"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
index fdf4160b1bb0..32f02a45608e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Увімкнено адміністратором"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Вимкнено адміністратором"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Увімкнено Додатковим захистом"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Вимкнено Додатковим захистом"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
index b40cb6827523..f3752d90497c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"منتظم کی طرف سے فعال کردہ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"منتظم کی طرف سے غیر فعال کردہ"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"اعلی تحفظ نے فعال کیا ہے"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"اعلی تحفظ نے غیر فعال کیا ہے"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
index 9a27735407e2..e2e9f423f501 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administrator tomonidan yoqilgan"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administrator tomonidan faolsizlantirilgan"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Kuchaytirilgan himoya tomonidan yoqilgan"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Kuchaytirilgan himoya tomonidan faolsizlantirilgan"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
index 3436762f8355..dd654b290977 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Do quản trị viên bật"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Quản trị viên đã vô hiệu hóa chế độ này"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Được bật bởi chế độ Bảo vệ nâng cao"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Bị tắt bởi chế độ Bảo vệ nâng cao"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
index 5c9e302f274c..8fa969ea5112 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"已被管理员启用"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"已被管理员停用"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"已被“高级保护”功能启用"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"已被“高级保护”功能停用"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
index d4b883355cf3..501f86084340 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"已由管理員啟用"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"已由管理員停用"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"已由進階保護功能啟用"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"已由進階保護功能停用"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
index d4b883355cf3..501f86084340 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"已由管理員啟用"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"已由管理員停用"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"已由進階保護功能啟用"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"已由進階保護功能停用"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
index 2a93d00d687f..86a6acb92df4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
@@ -19,6 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Kunikwe amandla umlawuli"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Kukhutshazwe umlawuli"</string>
- <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Kunikwe Amandla Ukuvikela Okuthuthukile"</string>
- <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Kukhutshazwe Ukuvikela Okuthuthukile"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
index 75809730a514..2ffdc930ccea 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
@@ -21,8 +21,4 @@
<string name="enabled_by_admin">Enabled by admin</string>
<!-- Summary for switch preference to denote it is switched off by an admin [CHAR LIMIT=50] -->
<string name="disabled_by_admin">Disabled by admin</string>
- <!-- Summary for switch preference to denote it is switched on by Advanced protection [CHAR LIMIT=50] -->
- <string name="enabled_by_advanced_protection">Enabled by Advanced Protection</string>
- <!-- Summary for switch preference to denote it is switched off by Advanced protection [CHAR LIMIT=50] -->
- <string name="disabled_by_advanced_protection">Disabled by Advanced Protection</string>
</resources>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml
index a79d69dbff8c..a79d69dbff8c 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
index cde8b332f2e7..465b6ccf4d9c 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
@@ -238,7 +238,10 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference {
} else {
setWidgetLayoutResource(R.layout.settingslib_preference_widget_radiobutton);
}
- setLayoutResource(R.layout.preference_selector_with_widget);
+ int resID = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_preference_selector_with_widget
+ : R.layout.preference_selector_with_widget;
+ setLayoutResource(resID);
setIconSpaceReserved(false);
final TypedArray a =
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
index 0f6a2a082e0c..1170f1e7c695 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
@@ -19,6 +19,7 @@ package com.android.settingslib.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import android.widget.Spinner;
@@ -110,7 +111,6 @@ public class SettingsSpinnerPreference extends Preference
notifyChanged();
}
-
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
@@ -119,6 +119,18 @@ public class SettingsSpinnerPreference extends Preference
spinner.setSelection(mPosition);
spinner.setOnItemSelectedListener(mOnSelectedListener);
spinner.setLongClickable(false);
+ spinner.setAccessibilityDelegate(
+ new View.AccessibilityDelegate() {
+ @Override
+ public void sendAccessibilityEvent(View host, int eventType) {
+ if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) {
+ // Ignore the INTERRUPT events TYPE_VIEW_SELECTED or Talkback will speak
+ // for it while fragment updating.
+ return;
+ }
+ super.sendAccessibilityEvent(host, eventType);
+ }
+ });
if (mShouldPerformClick) {
mShouldPerformClick = false;
// To show dropdown view.
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
index 0446873126b7..9aa0bc39c5d8 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
@@ -19,12 +19,16 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
<corners
android:radius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
index 25a936deade5..554cba565383 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
@@ -19,7 +19,9 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -28,6 +30,8 @@
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
index db2800e0ec41..c0c08699cc2a 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
@@ -19,7 +19,8 @@
<item
android:bottom="16dp"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -28,7 +29,8 @@
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius"
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
+ <padding android:bottom="16dp" />
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
index 98f95d927fa6..543b237373fb 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
@@ -19,7 +19,9 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -28,6 +30,8 @@
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
index c4286fdf020c..b89a0ddcdec5 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
@@ -19,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -27,4 +28,4 @@
android:radius="4dp" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
index 194cdb00a337..8099d9b3d7f7 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
@@ -18,7 +18,8 @@
android:color="?android:colorControlHighlight">
<item
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -26,4 +27,4 @@
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
index 8bc2f2f9ccd1..6d2cd1a51620 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
@@ -19,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -27,4 +28,4 @@
android:radius="4dp" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
index 2341661528d9..a119a4ae083f 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
@@ -19,12 +19,14 @@
<item
android:bottom="16dp"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
<corners android:radius="@dimen/settingslib_preference_corner_radius" />
+ <padding android:bottom="16dp" />
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
index 99704f2df190..bcdbf1d19545 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
@@ -19,12 +19,16 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
<corners
android:radius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
index 3a5938688f34..7955e4418ae9 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
@@ -19,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -30,4 +31,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
index edace29df37a..052eb01cab8d 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
@@ -19,7 +19,8 @@
<item
android:color="?android:attr/colorAccent"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -31,4 +32,4 @@
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
index b2d6d9da6af8..d4b658c384e6 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
@@ -19,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -30,4 +31,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</ripple>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
index 22cd87307a42..8d12f01e24ed 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.widget
-import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater;
import android.view.View
@@ -25,7 +24,6 @@ import androidx.annotation.CallSuper
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.RecyclerView
-import com.android.settingslib.widget.theme.R
/** Base class for Settings to use PreferenceFragmentCompat */
abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
@@ -45,7 +43,6 @@ abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
// Don't allow any divider in between the preferences in expressive design.
setDivider(null)
- this.listView.addItemDecoration(MarginItemDecoration())
}
}
@@ -54,18 +51,4 @@ abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
return SettingsPreferenceGroupAdapter(preferenceScreen)
return super.onCreateAdapter(preferenceScreen)
}
-
- internal class MarginItemDecoration() : RecyclerView.ItemDecoration() {
- override fun getItemOffsets(
- outRect: Rect,
- view: View,
- parent: RecyclerView,
- state: RecyclerView.State,
- ) {
- with(outRect) {
- bottom =
- view.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_radius_extrasmall1)
- }
- }
- }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
index a8483308556d..6f37f0cc5799 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
@@ -46,7 +46,7 @@ internal data class BlockedByAdminImpl(
) : BlockedByAdmin {
override fun getSummary(checked: Boolean?) = when (checked) {
true -> enterpriseRepository.getAdminSummaryString(
- advancedProtectionStringId = R.string.enabled_by_advanced_protection,
+ advancedProtectionStringId = com.android.settingslib.R.string.enabled,
updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
resId = R.string.enabled_by_admin,
enforcedAdmin = enforcedAdmin,
@@ -54,7 +54,7 @@ internal data class BlockedByAdminImpl(
)
false -> enterpriseRepository.getAdminSummaryString(
- advancedProtectionStringId = R.string.disabled_by_advanced_protection,
+ advancedProtectionStringId = com.android.settingslib.R.string.disabled,
updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
resId = R.string.disabled_by_admin,
enforcedAdmin = enforcedAdmin,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt
new file mode 100644
index 000000000000..db7a640be893
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.settingsprovider
+
+import android.content.ContentResolver
+import android.content.Context
+import android.provider.Settings
+import com.android.settingslib.spaprivileged.database.contentChangeFlow
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+fun Context.settingsSystemInteger(
+ name: String,
+ defaultValue: Int
+): ReadWriteProperty<Any?, Int> = SettingsSystemIntegerDelegate(this, name, defaultValue)
+
+fun Context.settingsSystemIntegerFlow(name: String, defaultValue: Int): Flow<Int> {
+ val value by settingsSystemInteger(name, defaultValue)
+ return contentChangeFlow(Settings.System.getUriFor(name))
+ .map { value }
+ .distinctUntilChanged()
+ .conflate()
+ .flowOn(Dispatchers.IO)
+}
+
+private class SettingsSystemIntegerDelegate(
+ context: Context,
+ private val name: String,
+ private val defaultValue: Int,
+) : ReadWriteProperty<Any?, Int> {
+
+ private val contentResolver: ContentResolver = context.contentResolver
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): Int =
+ Settings.System.getInt(contentResolver, name, defaultValue)
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
+ Settings.System.putInt(contentResolver, name, value)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp
new file mode 100644
index 000000000000..e3faf73a69fb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES 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_license"],
+}
+
+android_app {
+ name: "SpaPrivilegedRoboTestStub",
+ defaults: [
+ "SpaPrivilegedLib-defaults",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
+}
+
+android_robolectric_test {
+ name: "SpaPrivilegedRoboTests",
+ srcs: [
+ ":SpaPrivilegedLib_srcs",
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ defaults: [
+ "SpaPrivilegedLib-defaults",
+ ],
+
+ static_libs: [
+ "SpaLibTestUtils",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ ],
+
+ java_resource_dirs: [
+ "config",
+ ],
+
+ instrumentation_for: "SpaPrivilegedRoboTestStub",
+
+ test_options: {
+ timeout: 36000,
+ },
+
+ strict_mode: false,
+}
diff --git a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml b/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml
index de0a6201cb09..113852d74f69 100644
--- a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2025 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -12,14 +12,14 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <!-- gradient from 25% in the center to 100% at edges -->
- <gradient
- android:type="radial"
- android:gradientRadius="40%p"
- android:startColor="#AE000000"
- android:endColor="#00000000" />
-</shape> \ No newline at end of file
+ ~ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ coreApp="true"
+ package="com.android.settingslib.spaprivileged.settingsprovider">
+
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <application/>
+</manifest> \ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties b/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties
new file mode 100644
index 000000000000..95a24bde00f6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties
@@ -0,0 +1,16 @@
+/*
+* Copyright (C) 2025 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+sdk=NEWEST_SDK \ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt
new file mode 100644
index 000000000000..67e4180a624b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.spaprivileged.settingsprovider
+
+import android.content.Context
+import android.provider.Settings
+
+import androidx.test.core.app.ApplicationProvider
+
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.google.common.truth.Truth.assertThat
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SettingsSystemIntegerTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Before
+ fun setUp() {
+ Settings.System.putString(context.contentResolver, TEST_NAME, null)
+ }
+
+ @Test
+ fun setIntValue_returnSameValueByDelegate() {
+ val settingValue = 250
+
+ Settings.System.putInt(context.contentResolver, TEST_NAME, settingValue)
+
+ val value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+
+ assertThat(value).isEqualTo(settingValue)
+ }
+
+ @Test
+ fun setZero_returnZeroByDelegate() {
+ val settingValue = 0
+ Settings.System.putInt(context.contentResolver, TEST_NAME, settingValue)
+
+ val value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+
+ assertThat(value).isEqualTo(settingValue)
+ }
+
+ @Test
+ fun setValueByDelegate_getValueFromSettings() {
+ val settingsValue = 5
+ var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+
+ value = settingsValue
+
+ assertThat(Settings.System.getInt(context.contentResolver, TEST_NAME, TEST_SETTING_DEFAULT_VALUE)).isEqualTo(settingsValue)
+ }
+
+ @Test
+ fun setZeroByDelegate_getZeroFromSettings() {
+ val settingValue = 0
+ var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+
+ value = settingValue
+
+ assertThat(Settings.System.getInt(context.contentResolver, TEST_NAME, TEST_SETTING_DEFAULT_VALUE)).isEqualTo(settingValue)
+ }
+
+ @Test
+ fun setValueByDelegate_returnValueFromsettingsSystemIntegerFlow() = runBlocking<Unit> {
+ val settingValue = 7
+ var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+ value = settingValue
+
+ val flow = context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+
+ assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(settingValue)
+ }
+
+ @Test
+ fun setValueByDelegateTwice_collectAfterValueChanged_onlyKeepLatest() = runBlocking<Unit> {
+ val firstSettingValue = 5
+ val secondSettingValue = 10
+
+ var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+ value = firstSettingValue
+
+ val flow = context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+ value = secondSettingValue
+
+ assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(value)
+ }
+
+ @Test
+ fun settingsSystemIntegerFlow_collectBeforeValueChanged_getBoth() = runBlocking<Unit> {
+ val firstSettingValue = 12
+ val secondSettingValue = 17
+ val delay_ms = 100L
+
+ var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE)
+ value = firstSettingValue
+
+
+ val listDeferred = async {
+ context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE).toListWithTimeout()
+ }
+
+ delay(delay_ms)
+ value = secondSettingValue
+
+ assertThat(listDeferred.await())
+ .containsAtLeast(firstSettingValue, secondSettingValue).inOrder()
+ }
+
+ private companion object {
+ const val TEST_NAME = "test_system_integer_delegate"
+ const val TEST_SETTING_DEFAULT_VALUE = -1
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/unit/Android.bp
index 458fcc97aa9b..458fcc97aa9b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/Android.bp
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/unit/AndroidManifest.xml
index 8d384e8ca02e..8d384e8ca02e 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/AndroidManifest.xml
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/unit/res/values/strings.xml
index bdc0ba8224de..bdc0ba8224de 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/res/values/strings.xml
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
index 9cb33d2e9b2c..9cb33d2e9b2c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index 772f925c0a77..772f925c0a77 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
index 7220848eebff..7220848eebff 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
index 4221f9fb5111..4221f9fb5111 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index fd4b189c51ff..fd4b189c51ff 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 4d9d6da582b1..4d9d6da582b1 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 74a7c146b2ab..74a7c146b2ab 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
index 9f80b92548d2..9f80b92548d2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
index 97c74411d945..97c74411d945 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
index 70e405557dc7..70e405557dc7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
index e8ec974bb0b8..e8ec974bb0b8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
index 7f9e98b95fb7..7f9e98b95fb7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
index e10619e01c4e..e10619e01c4e 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
index 7c928389d08f..7c928389d08f 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
index 7ef11eb865ba..7ef11eb865ba 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
index f3245c9085e7..189bf363420c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
@@ -77,8 +77,8 @@ class RestrictedModeTest {
if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context,
RESTRICTION, userId)) {
return when (advancedProtectionStringId) {
- R.string.enabled_by_advanced_protection -> ENABLED_BY_ADVANCED_PROTECTION
- R.string.disabled_by_advanced_protection -> DISABLED_BY_ADVANCED_PROTECTION
+ com.android.settingslib.R.string.enabled -> ENABLED
+ com.android.settingslib.R.string.disabled -> DISABLED
else -> ""
}
}
@@ -129,7 +129,7 @@ class RestrictedModeTest {
val summary = blockedByAdmin.getSummary(true)
- assertThat(summary).isEqualTo(ENABLED_BY_ADVANCED_PROTECTION)
+ assertThat(summary).isEqualTo(ENABLED)
}
@RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
@@ -148,7 +148,7 @@ class RestrictedModeTest {
val summary = blockedByAdmin.getSummary(false)
- assertThat(summary).isEqualTo(DISABLED_BY_ADVANCED_PROTECTION)
+ assertThat(summary).isEqualTo(DISABLED)
}
@RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
@@ -202,7 +202,7 @@ class RestrictedModeTest {
const val ENABLED_BY_ADMIN = "Enabled by admin"
const val DISABLED_BY_ADMIN = "Disabled by admin"
- const val ENABLED_BY_ADVANCED_PROTECTION = "Enabled by advanced protection"
- const val DISABLED_BY_ADVANCED_PROTECTION = "Disabled by advanced protection"
+ const val ENABLED = "Enabled"
+ const val DISABLED = "Disabled"
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt
index cd747cc142c1..cd747cc142c1 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt
index c1d298d0b613..c1d298d0b613 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt
index ecc92f8f8d5c..ecc92f8f8d5c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt
index e3d182bb5ace..e3d182bb5ace 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index 72a5bd76e737..72a5bd76e737 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index 4d90076f060e..4d90076f060e 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
index 73dd295de77b..73dd295de77b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index c6409e7738d6..c6409e7738d6 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
index d3cfb2d71116..d3cfb2d71116 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index 60eccd987724..60eccd987724 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index 4f42c8254c39..4f42c8254c39 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
index 798e2d49ff57..798e2d49ff57 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 79085af63c6d..308b285e0cfc 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -175,13 +175,7 @@ class TogglePermissionAppListPageTest {
val summary = getSummary(listModel)
- assertThat(summary)
- .isEqualTo(
- context.getString(
- com.android.settingslib.widget.restricted.R.string
- .disabled_by_advanced_protection
- )
- )
+ assertThat(summary).isEqualTo(context.getString(com.android.settingslib.R.string.disabled))
}
@RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
index 1818f2d92f9c..1818f2d92f9c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
index e450364a9ab2..e450364a9ab2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
index 55c16bd20336..55c16bd20336 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
index eadf0ca0686d..eadf0ca0686d 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
index 1fd7ecf3cf40..1fd7ecf3cf40 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
index bdff89f6d69b..bdff89f6d69b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 4068bceb1475..4068bceb1475 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
index d5e8d6a5fa13..d5e8d6a5fa13 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
index a7a153ba479c..a7a153ba479c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
index 000743e7ef8a..000743e7ef8a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
index 354bbf536a85..354bbf536a85 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index a029f56cf1d7..349d13a29b05 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -37,13 +37,6 @@ flag {
}
flag {
- name: "enable_hide_exclusively_managed_bluetooth_device"
- namespace: "dck_framework"
- description: "Hide exclusively managed Bluetooth devices in BT settings menu."
- bug: "324475542"
-}
-
-flag {
name: "enable_set_preferred_transport_for_le_audio_device"
namespace: "bluetooth"
description: "Enable setting preferred transport for Le Audio device"
@@ -249,3 +242,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "audio_stream_media_service_by_receive_state"
+ namespace: "cross_device_experiences"
+ description: "Start or update audio stream media service by receive state"
+ bug: "398700619"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/drawable/ic_news.xml b/packages/SettingsLib/res/drawable/ic_news.xml
new file mode 100644
index 000000000000..90615ec1b8fd
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_news.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="14.933333dp" android:viewportHeight="14" android:viewportWidth="15" android:width="16dp">
+ <path android:fillColor="#ffffff" android:pathData="M3,10.8H11V9.6H3V10.8ZM3,8.4H5.4V4.8H3V8.4ZM12.2,13.6H1.8C1.467,13.6 1.183,13.483 0.95,13.25C0.717,13.017 0.6,12.733 0.6,12.4V3.6C0.6,3.267 0.717,2.983 0.95,2.75C1.183,2.517 1.467,2.4 1.8,2.4H6.683C6.528,2.711 6.406,3.033 6.317,3.367C6.239,3.7 6.2,4.044 6.2,4.4C6.2,4.933 6.283,5.439 6.45,5.917C6.628,6.383 6.878,6.811 7.2,7.2H6.6V8.4H8.767C9.056,8.522 9.35,8.622 9.65,8.7C9.961,8.767 10.278,8.8 10.6,8.8C11.111,8.8 11.606,8.717 12.083,8.55C12.572,8.372 13.011,8.122 13.4,7.8V12.4C13.4,12.733 13.283,13.017 13.05,13.25C12.817,13.483 12.533,13.6 12.2,13.6ZM10.6,8C10.6,7 10.25,6.15 9.55,5.45C8.85,4.75 8,4.4 7,4.4C8,4.4 8.85,4.05 9.55,3.35C10.25,2.65 10.6,1.8 10.6,0.8C10.6,1.8 10.95,2.65 11.65,3.35C12.35,4.05 13.2,4.4 14.2,4.4C13.2,4.4 12.35,4.75 11.65,5.45C10.95,6.15 10.6,7 10.6,8Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_promotions.xml b/packages/SettingsLib/res/drawable/ic_promotions.xml
new file mode 100644
index 000000000000..a597ecebd967
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_promotions.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="15" android:viewportWidth="13" android:width="13.866667dp">
+ <path android:fillColor="#ffffff" android:pathData="M1.4,13.4C1.067,13.4 0.783,13.283 0.55,13.05C0.317,12.817 0.2,12.533 0.2,12.2V4.6C0.2,4.267 0.317,3.983 0.55,3.75C0.783,3.517 1.067,3.4 1.4,3.4H2.6V3.133C2.6,2.467 2.822,1.878 3.267,1.367C3.722,0.856 4.3,0.6 5,0.6C5.667,0.6 6.233,0.833 6.7,1.3C7.167,1.767 7.4,2.333 7.4,3V3.4H8.6C8.933,3.4 9.217,3.517 9.45,3.75C9.683,3.983 9.8,4.267 9.8,4.6V6.367C9.611,6.311 9.417,6.272 9.217,6.25C9.017,6.217 8.811,6.2 8.6,6.2C7.378,6.2 6.339,6.628 5.483,7.483C4.628,8.339 4.2,9.378 4.2,10.6C4.2,11.111 4.283,11.611 4.45,12.1C4.628,12.578 4.878,13.011 5.2,13.4H1.4ZM8.6,14.2C8.6,13.2 8.25,12.35 7.55,11.65C6.85,10.95 6,10.6 5,10.6C6,10.6 6.85,10.25 7.55,9.55C8.25,8.85 8.6,8 8.6,7C8.6,8 8.95,8.85 9.65,9.55C10.35,10.25 11.2,10.6 12.2,10.6C11.2,10.6 10.35,10.95 9.65,11.65C8.95,12.35 8.6,13.2 8.6,14.2ZM3.8,3.4H6.2V3C6.2,2.667 6.083,2.383 5.85,2.15C5.617,1.917 5.333,1.8 5,1.8C4.667,1.8 4.383,1.917 4.15,2.15C3.917,2.383 3.8,2.667 3.8,3V3.4ZM3.2,5.8C3.367,5.8 3.506,5.744 3.617,5.633C3.739,5.511 3.8,5.367 3.8,5.2V4.6H2.6V5.2C2.6,5.367 2.656,5.511 2.767,5.633C2.889,5.744 3.033,5.8 3.2,5.8ZM6.8,5.8C6.967,5.8 7.106,5.744 7.217,5.633C7.339,5.511 7.4,5.367 7.4,5.2V4.6H6.2V5.2C6.2,5.367 6.256,5.511 6.367,5.633C6.489,5.744 6.633,5.8 6.8,5.8Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_recs.xml b/packages/SettingsLib/res/drawable/ic_recs.xml
new file mode 100644
index 000000000000..034ff9e221b0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_recs.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="16dp"
+ android:viewportWidth="14"
+ android:viewportHeight="16">
+ <path
+ android:pathData="M6,15.2L3.6,12.8H1.6C1.278,12.8 0.994,12.683 0.75,12.45C0.517,12.206 0.4,11.922 0.4,11.6V2.8C0.4,2.478 0.517,2.2 0.75,1.967C0.994,1.722 1.278,1.6 1.6,1.6H6.2C5.878,1.989 5.628,2.428 5.45,2.917C5.283,3.394 5.2,3.889 5.2,4.4C5.2,5.622 5.628,6.661 6.483,7.517C7.339,8.372 8.378,8.8 9.6,8.8C9.956,8.8 10.3,8.761 10.633,8.683C10.967,8.594 11.289,8.472 11.6,8.317V11.6C11.6,11.922 11.483,12.206 11.25,12.45C11.017,12.683 10.733,12.8 10.4,12.8H8.4L6,15.2ZM9.6,8C9.6,7 9.25,6.15 8.55,5.45C7.85,4.75 7,4.4 6,4.4C7,4.4 7.85,4.05 8.55,3.35C9.25,2.65 9.6,1.8 9.6,0.8C9.6,1.8 9.95,2.65 10.65,3.35C11.35,4.05 12.2,4.4 13.2,4.4C12.2,4.4 11.35,4.75 10.65,5.45C9.95,6.15 9.6,7 9.6,8Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_social.xml b/packages/SettingsLib/res/drawable/ic_social.xml
new file mode 100644
index 000000000000..01974319b441
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_social.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="15" android:viewportWidth="15" android:width="16dp">
+ <path android:fillColor="#ffffff" android:pathData="M0.6,14.4V12.733C0.6,12.411 0.678,12.117 0.833,11.85C0.989,11.583 1.211,11.378 1.5,11.233C2.056,10.956 2.628,10.75 3.217,10.617C3.817,10.472 4.428,10.4 5.05,10.4C5.661,10.4 6.256,10.472 6.833,10.617C7.422,10.75 7.989,10.956 8.533,11.233C8.811,11.378 9.028,11.583 9.183,11.85C9.339,12.117 9.417,12.411 9.417,12.733V14.4H0.6ZM10.6,14.4V12.617C10.6,12.183 10.494,11.778 10.283,11.4C10.072,11.022 9.772,10.733 9.383,10.533C9.772,10.6 10.144,10.7 10.5,10.833C10.856,10.956 11.206,11.106 11.55,11.283C11.828,11.439 12.072,11.628 12.283,11.85C12.494,12.061 12.6,12.317 12.6,12.617V14.4H10.6ZM5.2,9.6C4.589,9.6 4.067,9.389 3.633,8.967C3.211,8.533 3,8.011 3,7.4C3,6.789 3.211,6.272 3.633,5.85C4.067,5.417 4.589,5.2 5.2,5.2C5.811,5.2 6.328,5.417 6.75,5.85C7.183,6.272 7.4,6.789 7.4,7.4C7.4,8.011 7.183,8.533 6.75,8.967C6.328,9.389 5.811,9.6 5.2,9.6ZM10.6,7.4C10.6,8.011 10.383,8.533 9.95,8.967C9.528,9.389 9.011,9.6 8.4,9.6C8.311,9.6 8.217,9.594 8.117,9.583C8.017,9.572 7.922,9.55 7.833,9.517C8.089,9.206 8.278,8.872 8.4,8.517C8.533,8.161 8.6,7.789 8.6,7.4C8.6,7.011 8.533,6.639 8.4,6.283C8.278,5.928 8.089,5.594 7.833,5.283C7.922,5.25 8.017,5.228 8.117,5.217C8.217,5.206 8.311,5.2 8.4,5.2C9.011,5.2 9.528,5.417 9.95,5.85C10.383,6.272 10.6,6.789 10.6,7.4ZM11.4,6.4C11.4,5.622 11.128,4.961 10.583,4.417C10.039,3.872 9.378,3.6 8.6,3.6C9.378,3.6 10.039,3.328 10.583,2.783C11.128,2.239 11.4,1.578 11.4,0.8C11.4,1.578 11.672,2.239 12.217,2.783C12.761,3.328 13.422,3.6 14.2,3.6C13.422,3.6 12.761,3.872 12.217,4.417C11.672,4.961 11.4,5.622 11.4,6.4Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index dcad7356508d..58ddc723b77a 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Beheer deur Beperkte Instellings"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Onbeskikbaar tydens oproepe"</string>
<string name="disabled" msgid="8017887509554714950">"Gedeaktiveer"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Toegelaat"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nie toegelaat nie"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installeer onbekende apps"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index dc6bd0fb06c8..3844a4b69d86 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"በተገደበ ቅንብር ቁጥጥር የሚደረግበት"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"በጥሪዎች ጊዜ አይገኝም"</string>
<string name="disabled" msgid="8017887509554714950">"ቦዝኗል"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ይፈቀዳል"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"አይፈቀድም"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ያልታወቁ መተግበሪያዎችን ይጫኑ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 5fbab96a6a01..0d31c2eb0b96 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"يتحكّم فيه إعداد محظور"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"غير متاح أثناء المكالمات"</string>
<string name="disabled" msgid="8017887509554714950">"غير مفعّل"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"تطبيق مسموح به"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"تطبيق غير مسموح به"</string>
<string name="install_other_apps" msgid="3232595082023199454">"تثبيت التطبيقات غير المعروفة"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index f95a66befec1..56f73df512a1 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"প্ৰতিবন্ধিত ছেটিঙৰ দ্বাৰা নিয়ন্ত্ৰিত"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"কল চলি থকাৰ সময়ত উপলব্ধ নহয়"</string>
<string name="disabled" msgid="8017887509554714950">"নিষ্ক্ৰিয়"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"অনুমতি দিয়া হৈছে"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"অনুমতি দিয়া হোৱা নাই"</string>
<string name="install_other_apps" msgid="3232595082023199454">"অজ্ঞাত এপ্ ইনষ্টল কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 5e47ff955125..b7dcfb857902 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Məhdudlaşdırılmış Ayar ilə nəzarət edilir"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Zənglər zamanı əlçatan deyil"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktiv"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"İcazə verilib"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"İcazə verilməyib"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Tanınmayan tətbiqlərin quraşdırılması"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index cb623991538d..97873e5a2d2a 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolišu ograničena podešavanja"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Nedostupno tokom poziva"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Dozvoljeno"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nije dozvoljeno"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instaliranje nepoznatih aplikacija"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index ef66fb32d844..78a5eaf388f0 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Пад кіраваннем Абмежаванага наладжвання"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Недаступна падчас выклікаў"</string>
<string name="disabled" msgid="8017887509554714950">"Адключанае"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Дазволена"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Забаронена"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Усталёўка невядомых праграм"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 69d68662b8c5..9fc0dea9abea 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Управлява се чрез ограничена настройка"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Заето по време на обаждания"</string>
<string name="disabled" msgid="8017887509554714950">"Деактивирано"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Има разрешение"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Няма разрешение"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Инст. на неизвестни прилож."</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 7e8e3fe3de70..79e4ed9600aa 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"এটি বিধিনিষেধ সেটিং থেকে নিয়ন্ত্রণ করা হয়"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"কল চলাকালীন উপলভ্য হবে না"</string>
<string name="disabled" msgid="8017887509554714950">"অক্ষম হয়েছে"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"অনুমোদিত"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"অনুমোদিত নয়"</string>
<string name="install_other_apps" msgid="3232595082023199454">"অজানা অ্যাপ ইনস্টল করা"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index d0b15d9e6768..d06ce341fa99 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolira ograničena postavka"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Nije dostupno tokom poziva"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Dozvoljeno"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nije dozvoljeno"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instaliranje nepoznatih aplikacija"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 283182c7219d..776caae3b828 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -507,7 +507,7 @@
<string name="power_discharge_by_only_enhanced" msgid="3268796172652988877">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en facis"</string>
<string name="power_discharge_by" msgid="4113180890060388350">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_discharge_by_only_short" msgid="5883041507426914446">"fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_discharge_by_only_short" msgid="5883041507426914446">"Fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"És possible que la bateria s\'esgoti a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Temps restant inferior a <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Temps restant inferior a <xliff:g id="THRESHOLD">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlat per l\'opció de configuració restringida"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"No està disponible durant les trucades"</string>
<string name="disabled" msgid="8017887509554714950">"Desactivat"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Amb permís"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Sense permís"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instal·la aplicacions desconegudes"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index bc00d47af14f..02722fd98959 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Spravováno omezeným nastavením"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Při volání nedostupné"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktivováno"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Povoleno"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Není povoleno"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalace neznámých aplikací"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index ef1448316f26..4d37578c68b9 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Styres af en begrænset indstilling"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Kan ikke bruges under opkald"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktiveret"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Tilladt"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Ikke tilladt"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installer ukendte apps"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index e7ffaa4f6048..3e57002a7a59 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Gesteuert durch eingeschränkte Einstellung"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Während Anrufen nicht verfügbar"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktiviert"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Zugelassen"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nicht zugelassen"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installieren unbekannter Apps"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 2fc0ff2fc1db..be1135690e62 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ελέγχεται από τη Ρύθμιση με περιορισμό"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Μη διαθέσιμη κατά τη διάρκεια κλήσεων"</string>
<string name="disabled" msgid="8017887509554714950">"Απενεργοποιημένη"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Επιτρέπεται"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Δεν επιτρέπεται"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Εγκατ. άγνωστων εφ."</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 34d6bf7299f2..8c819ac742a1 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Unavailable during calls"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Install unknown apps"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 120343727593..2faf2feed627 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -539,6 +539,7 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Unavailable during calls"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
+ <string name="enabled" msgid="3997122818554810678">"Enabled"</string>
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Install unknown apps"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 34d6bf7299f2..8c819ac742a1 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Unavailable during calls"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Install unknown apps"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 34d6bf7299f2..8c819ac742a1 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Unavailable during calls"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Install unknown apps"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ca17178b0db0..224ad41a5ad2 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Función controlada por configuración restringida"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"No disponible durante llamadas"</string>
<string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Con permiso"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"No permitida"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalar apps desconocidas"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 22e8b114f875..66d81d27a807 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlado por ajustes restringidos"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"No disponible durante las llamadas"</string>
<string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Autorizadas"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"No autorizadas"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalar aplicaciones desconocidas"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 0de019794528..59e1e4b4bc95 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Haldavad piiranguga seaded"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Pole kõnede ajal saadaval"</string>
<string name="disabled" msgid="8017887509554714950">"Keelatud"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Lubatud"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Pole lubatud"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Tundmatute rakenduste installimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 7a8bf56841b6..6fd7e24b8c8b 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ezarpen mugatuak kontrolatzen du"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Ez dago erabilgarri deiak egin bitartean"</string>
<string name="disabled" msgid="8017887509554714950">"Desgaituta"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Baimenduta"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Baimendu gabe"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalatu aplikazio ezezagunak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 9ba2f3510980..d42a7a0acb3c 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"با تنظیم «حالت محدود» کنترل می‌شود"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"درطول تماس دردسترس نیست"</string>
<string name="disabled" msgid="8017887509554714950">"غیر فعال شد"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"مجاز بودن"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"مجاز نبودن"</string>
<string name="install_other_apps" msgid="3232595082023199454">"نصب برنامه‌های ناشناس"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index d1cc76cab9aa..a66c8cf375a5 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -246,7 +246,7 @@
<item msgid="6946761421234586000">"400 %"</item>
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
- <string name="category_personal" msgid="6236798763159385225">"Henkilökohtainen"</string>
+ <string name="category_personal" msgid="6236798763159385225">"Omat"</string>
<string name="category_work" msgid="4014193632325996115">"Työ"</string>
<string name="category_private" msgid="4244892185452788977">"Yksityinen"</string>
<string name="category_clone" msgid="1554511758987195974">"Klooni"</string>
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Rajoitettujen asetusten mukaisesti"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Ei käytettävissä puhelujen aikana"</string>
<string name="disabled" msgid="8017887509554714950">"Pois päältä"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Sallittu"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Ei sallittu"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Tuntemattomien sovellusten asentaminen"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index d83389261c71..f332208d742a 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Contrôlé par les paramètres restreints"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Indisponible pendant les appels"</string>
<string name="disabled" msgid="8017887509554714950">"Désactivée"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Autorisée"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Non autorisée"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installer les applis inconnues"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 2fc946b9ed9f..4cc8a9a37632 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -81,7 +81,7 @@
<string name="speed_label_fast" msgid="2677719134596044051">"Élevée"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Très rapide"</string>
<string name="wifi_passpoint_expired" msgid="6540867261754427561">"Arrivé à expiration"</string>
- <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+ <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Déconnecté"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Déconnexion…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Connexion…"</string>
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Contrôlé par les paramètres restreints"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Indisponible pendant les appels"</string>
<string name="disabled" msgid="8017887509554714950">"Désactivée"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Autorisé"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Non autorisé"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installation d\'applis inconnues"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index b6eacfc33293..b7a6abc2dfcd 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Baixo o control de opcións restrinxidas"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Non dispoñible durante as chamadas"</string>
<string name="disabled" msgid="8017887509554714950">"Desactivada"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Permiso concedido"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Permiso non concedido"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalar aplicacións descoñecidas"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 57f898210efd..aa27f2a8a09b 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"પ્રતિબંધિત સેટિંગ દ્વારા નિયંત્રિત"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"કૉલ દરમિયાન અનુપલબ્ધ"</string>
<string name="disabled" msgid="8017887509554714950">"બંધ કરી"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"મંજૂરી છે"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"મંજૂરી નથી"</string>
<string name="install_other_apps" msgid="3232595082023199454">"અજાણી ઍપ ઇન્સ્ટૉલ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 02bc92be8899..7c4d64f32cda 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"इसे पाबंदी मोड वाली सेटिंग से कंट्रोल किया जाता है"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"कॉल के दौरान उपलब्ध नहीं है"</string>
<string name="disabled" msgid="8017887509554714950">"बंद किया गया"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"अनुमति है"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"अनुमति नहीं है"</string>
<string name="install_other_apps" msgid="3232595082023199454">"अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 15eb4710cbf8..ba5650f1d7a5 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolira ograničena postavka"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Nije dostupno tijekom poziva"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Dopušteno"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nije dopušteno"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalacija nepoznatih aplikacija"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 08ae0f870a9e..faeec8d6ed24 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Korlátozott beállítás vezérli"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Nem áll rendelkezésre hívások közben"</string>
<string name="disabled" msgid="8017887509554714950">"Letiltva"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Engedélyezett"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nem engedélyezett"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Ismeretlen alkalmazások telepítése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 347101354a3b..f2b22679b1bb 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Կառավարվում է սահմանափակ ռեժիմի կարգավորումներով"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Զանգի ընթացքում հասանելի չէ"</string>
<string name="disabled" msgid="8017887509554714950">"Կասեցված է"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Թույլատրված է"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Արգելված"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Անհայտ հավելվածների տեղադրում"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 85109fcf1677..7af3de0da4fe 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Dikontrol oleh Setelan Terbatas"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Tidak tersedia selama panggilan berlangsung"</string>
<string name="disabled" msgid="8017887509554714950">"Dinonaktifkan"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Diizinkan"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Tidak diizinkan"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instal aplikasi tidak dikenal"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 3041151c617e..e6ef20fcd908 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Stýrt af takmarkaði stillingu"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Ekki í boði á meðan á símtölum stendur"</string>
<string name="disabled" msgid="8017887509554714950">"Óvirkt"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Heimilað"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Ekki heimilað"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Setja upp óþekkt forrit"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index c42cab36f30d..24e7a4cfe45d 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Gestita tramite impostazioni con restrizioni"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Non disponibile durante le chiamate"</string>
<string name="disabled" msgid="8017887509554714950">"Disattivato"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Autorizzazione concessa"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Autorizzazione non concessa"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installa app sconosciute"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index c39521f8776f..82d90430090b 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"בשליטה של הגדרה מוגבלת"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ההעדפה הזו לא זמינה במהלך שיחות"</string>
<string name="disabled" msgid="8017887509554714950">"מושבת"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"מורשה"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"לא מורשה"</string>
<string name="install_other_apps" msgid="3232595082023199454">"התקנת אפליקציות לא מוכרות"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index bb997ff6294d..79438fa1fa2d 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"制限付き設定によって管理されています"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"通話中は利用できません"</string>
<string name="disabled" msgid="8017887509554714950">"無効"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"許可"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"許可しない"</string>
<string name="install_other_apps" msgid="3232595082023199454">"不明なアプリのインストール"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 91e8889acfa5..ea01938d93d9 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"კონტროლდება შეზღუდული რეჟიმის პარამეტრით"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"მიუწვდომელია ზარების განხორციელებისას"</string>
<string name="disabled" msgid="8017887509554714950">"გამორთული"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"დაშვებულია"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"დაუშვებელია"</string>
<string name="install_other_apps" msgid="3232595082023199454">"უცნობი აპების ინსტალაცია"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 25c92de40657..16b7c22b6bfe 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Шектелген параметрлер арқылы басқарылады."</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Қоңырау шалу кезінде қолжетімді емес."</string>
<string name="disabled" msgid="8017887509554714950">"Өшірілген"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Рұқсат берілген"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Рұқсат етілмеген"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Белгісіз қолданбаларды орнату"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 9dadee1d34b3..de93ee94271f 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"គ្រប់គ្រងដោយការកំណត់ដែលបានរឹតបន្តឹង"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"មិនអាច​ប្រើបានទេ​អំឡុងពេល​ហៅទូរសព្ទ"</string>
<string name="disabled" msgid="8017887509554714950">"បិទ"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"បាន​អនុញ្ញាត"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"មិន​បានអនុញ្ញាត​"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ដំឡើងកម្មវិធីដែលមិនស្គាល់"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 72056bb1ed7d..4c4ea64ea52f 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ನಿರ್ಬಂಧಿಸಲಾದ ಸೆಟ್ಟಿಂಗ್ ಮೂಲಕ ನಿಯಂತ್ರಿಸಲಾಗುತ್ತದೆ"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ಕರೆಗಳ ಸಮಯದಲ್ಲಿ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="disabled" msgid="8017887509554714950">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ಅನುಮತಿಸಲಾಗಿದೆ"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ಅನುಮತಿ ಇಲ್ಲ"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ಅಪರಿಚಿತ ಆ್ಯಪ್‍‍ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 3b965e54e145..0488543f7790 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"제한된 설정으로 제어됨"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"통화 중에는 사용할 수 없습니다."</string>
<string name="disabled" msgid="8017887509554714950">"사용 안함"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"허용됨"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"허용되지 않음"</string>
<string name="install_other_apps" msgid="3232595082023199454">"알 수 없는 앱 설치"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 3eee360ed220..b04883747ee7 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Чектелген параметр аркылуу көзөмөлдөнөт"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Сүйлөшүп жаткан учурда жеткиликсиз"</string>
<string name="disabled" msgid="8017887509554714950">"Өчүрүлгөн"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Уруксат берилген"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Тыюу салынган"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Белгисиз колдонмолорду орнотуу"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 8044cce18edb..f57ed0cea03b 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ຄວບຄຸມໂດຍການຕັ້ງຄ່າທີ່ຈຳກັດໄວ້"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ບໍ່ສາມາດໃຊ້ໄດ້ລະຫວ່າງການໂທ"</string>
<string name="disabled" msgid="8017887509554714950">"ປິດການນຳໃຊ້"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ອະນຸຍາດແລ້ວ"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ບໍ່ອະນຸຍາດ"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index aec236a55d41..25b22cae3d0e 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Valdoma pagal apribotą nustatymą"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Nepasiekiama per skambučius"</string>
<string name="disabled" msgid="8017887509554714950">"Neleidžiama"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Leidžiama"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Neleidžiama"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Nežinomų programų diegimas"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 5716f8f4194c..cdca19b8e0c8 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolē ierobežots iestatījums"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Ierīce nav pieejama zvanu laikā"</string>
<string name="disabled" msgid="8017887509554714950">"Atspējots"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Atļauts"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nav atļauts"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Nezināmu lietotņu instalēšana"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index bc6409da8e12..e51ca04022a7 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролирано со ограничени поставки"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Недостапно при повици"</string>
<string name="disabled" msgid="8017887509554714950">"Оневозможено"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Со дозвола"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Без дозвола"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Инсталирање непознати апликации"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index bcadd7d88059..54b6ff8ab524 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"നിയന്ത്രിത ക്രമീകരണം ഉപയോഗിച്ച് നിയന്ത്രിക്കുന്നത്"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"കോളുകൾ ചെയ്യുമ്പോൾ ലഭ്യമല്ല"</string>
<string name="disabled" msgid="8017887509554714950">"പ്രവർത്തനരഹിതമാക്കി"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"അനുവദനീയം"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"അനുവദിച്ചിട്ടില്ല"</string>
<string name="install_other_apps" msgid="3232595082023199454">"പരിചയമില്ലാത്ത ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 2b45e85a2995..ed42362e5c1e 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Хязгаарлагдсан тохиргоогоор хянадаг"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Дуудлагын үер боломжгүй"</string>
<string name="disabled" msgid="8017887509554714950">"Идэвхгүйжүүлсэн"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Зөвшөөрсөн"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Зөвшөөрөөгүй"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Тодорхойгүй апп суулгах"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 826d6015fc49..6e7cd02b1e61 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"प्रतिबंधित केलेल्या सेटिंग द्वारे नियंत्रित"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"कॉल दरम्‍यान उपलब्ध नाही"</string>
<string name="disabled" msgid="8017887509554714950">"अक्षम"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"अनुमती आहे"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"अनुमती नाही"</string>
<string name="install_other_apps" msgid="3232595082023199454">"अज्ञात अ‍ॅप्स इंस्टॉल करा"</string>
@@ -561,7 +563,7 @@
<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>
- <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"सिस्टम भाषा वापरा"</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>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index a2f95628a6a1..58d80385ff87 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Dikawal oleh Tetapan Terhad"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Tidak tersedia semasa panggilan berlangsung"</string>
<string name="disabled" msgid="8017887509554714950">"Dilumpuhkan"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Dibenarkan"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Tidak dibenarkan"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Pasang apl yang tidak diketahui"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index b0d86afe9a6c..ddd127901e18 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ကန့်သတ်ဆက်တင်ဖြင့် ထိန်းချုပ်ထားသည်"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ဖုန်းခေါ်ဆိုနေချိန်တွင် မရနိုင်ပါ"</string>
<string name="disabled" msgid="8017887509554714950">"ပိတ်ထားပြီး"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ခွင့်ပြုထားသည်"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ခွင့်မပြုပါ"</string>
<string name="install_other_apps" msgid="3232595082023199454">"အမည်မသိအက်ပ် ထည့်သွင်းခြင်း"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 755df5100e5a..c8fc415528b4 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrollert av en begrenset innstilling"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Utilgjengelig under samtaler"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktivert"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Tillatt"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Ikke tillatt"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installer ukjente apper"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 0a9ac1770896..5f09635e19c7 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"प्रतिबन्धित सेटिङले नियन्त्रण गरेको"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"कल चलिरहेका बेला उपलब्ध छैन"</string>
<string name="disabled" msgid="8017887509554714950">"असक्षम पारियो"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"अनुमति छ"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"अनुमति छैन"</string>
<string name="install_other_apps" msgid="3232595082023199454">"अज्ञात एप इन्स्टल गर्ने अनुमति"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index f71f3abfaf2b..78dfdcead634 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Beheerd door beperkte instelling"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Niet beschikbaar tijdens gesprekken"</string>
<string name="disabled" msgid="8017887509554714950">"Uitgezet"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Toegestaan"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Niet toegestaan"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Onbekende apps installeren"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index f30ae3d94d42..d6e26314a0f9 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ପ୍ରତିବନ୍ଧିତ ସେଟିଂ ଦ୍ୱାରା ନିୟନ୍ତ୍ରଣ କରାଯାଇଛି"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"କଲ କରିବାବେଳେ ଉପଲବ୍ଧ ନଥାଏ"</string>
<string name="disabled" msgid="8017887509554714950">"ଅକ୍ଷମ ହୋଇଛି"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ଅନୁମତି ଦିଆଯାଇଛି"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ଅନୁମତି ନାହିଁ"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ଅଜଣା ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 2c2eab2fa871..ab5f4afd07f3 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ਪ੍ਰਤਿਬੰਧਿਤ ਸੈਟਿੰਗ ਰਾਹੀਂ ਕੰਟਰੋਲ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ਕਾਲਾਂ ਦੌਰਾਨ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="disabled" msgid="8017887509554714950">"ਅਯੋਗ ਬਣਾਇਆ"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ਮਨਜ਼ੂਰਸ਼ੁਦਾ"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ਗੈਰ-ਮਨਜ਼ੂਰਸ਼ੁਦਾ"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ਅਗਿਆਤ ਐਪਾਂ ਦੀ ਸਥਾਪਨਾ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index aed9b06d5037..462142468e26 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Obowiązują ustawienia z ograniczonym dostępem"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Niedostępne w trakcie połączeń"</string>
<string name="disabled" msgid="8017887509554714950">"Wyłączona"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Dozwolone"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Niedozwolone"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalowanie nieznanych aplikacji"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index e16e3557dc38..482f47931f4a 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada pelas configurações restritas"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Indisponível durante ligações"</string>
<string name="disabled" msgid="8017887509554714950">"Desativado"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Permitido"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Não permitido"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalar apps desconhecidos"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index e4381d6131e7..1a9975732513 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -530,7 +530,7 @@
<string name="battery_info_status_charging_dock" msgid="8573274094093364791">"A carregar"</string>
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string>
<string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ligado, mas não está a carregar"</string>
- <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
+ <string name="battery_info_status_full" msgid="1339002294876531312">"Bateria carregada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Totalmente carregada"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento em espera"</string>
<string name="battery_info_status_charging_v2" msgid="6118522107222245505">"Carregamento"</string>
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlado por uma definição restrita"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Indisponível durante as chamadas"</string>
<string name="disabled" msgid="8017887509554714950">"Desativada"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Autorizada"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Não autorizada"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalar apps desconhecidas"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index e16e3557dc38..482f47931f4a 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada pelas configurações restritas"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Indisponível durante ligações"</string>
<string name="disabled" msgid="8017887509554714950">"Desativado"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Permitido"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Não permitido"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalar apps desconhecidos"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 83a6e68b36b4..757fee9e110e 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlată de setarea restricționată"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Indisponibil în timpul apelurilor"</string>
<string name="disabled" msgid="8017887509554714950">"Dezactivată"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Permise"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nepermise"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalarea aplicațiilor necunoscute"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 1ed7c3623acc..9e922a0b85b3 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролируется настройками с ограниченным доступом"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Недоступно во время вызовов"</string>
<string name="disabled" msgid="8017887509554714950">"Отключено"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Разрешено"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Запрещено"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Установка неизвестных приложений"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index dffa392c7947..517b00c61efb 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"සීමා කළ සැකසීම මගින් පාලනය වේ"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ඇමතුම් අතරතුර නොපවතී"</string>
<string name="disabled" msgid="8017887509554714950">"අබල කර ඇත"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"ඉඩ දුන්"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ඉඩ නොදෙන"</string>
<string name="install_other_apps" msgid="3232595082023199454">"නොදන්නා යෙදුම් ස්ථාපනය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 0f4297aec6ad..e8fb53fdcef6 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ovládané obmedzeným nastavením"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Počas hovorov nie je k dispozícii"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktivované"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Povolené"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nie je povolené"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Inštalácia neznámych aplikácií"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 744b4da576ed..7d508862bef1 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Pod nadzorom omejene nastavitve"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Ni na voljo med klici"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogočeno"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Dovoljene"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Ni dovoljeno"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Nameščanje neznanih aplikacij"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 3d9622e063de..8a711d5ebcee 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrollohet nga \"Cilësimet e kufizuara\""</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Nuk ofrohet gjatë telefonatave"</string>
<string name="disabled" msgid="8017887509554714950">"Çaktivizuar"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Lejohet"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Nuk lejohet"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Instalo aplikacione të panjohura"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 32ec5000bfd0..d57eb5c4c830 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролишу ограничена подешавања"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Недоступно током позива"</string>
<string name="disabled" msgid="8017887509554714950">"Онемогућено"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Дозвољено"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Није дозвољено"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Инсталирање непознатих апликација"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index cf223f5b7bdb..5d27839daa6a 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Styrs av spärrad inställning"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Ej tillgänglig under samtal"</string>
<string name="disabled" msgid="8017887509554714950">"Inaktiverad"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Tillåts"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Tillåts inte"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Installera okända appar"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 26e0412b2893..9ddb7ff129ad 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Imedhibitiwa na Mpangilio wenye Mipaka"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Haipatikani wakati unaongea kwa simu"</string>
<string name="disabled" msgid="8017887509554714950">"Imezimwa"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Imeruhusiwa"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Hairuhusiwi"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Kuweka programu zisizojulikana"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index c2e762f417e7..68a60cf85a90 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"வரையறுக்கப்பட்ட அமைப்பால் கட்டுப்படுத்தப்படுகிறது"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"அழைப்புகளின்போது பயன்படுத்த முடியாது"</string>
<string name="disabled" msgid="8017887509554714950">"முடக்கப்பட்டது"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"அனுமதிக்கப்பட்டது"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"அனுமதிக்கப்படவில்லை"</string>
<string name="install_other_apps" msgid="3232595082023199454">"தெரியாத ஆப்ஸ்களை நிறுவுதல்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index f4678f3d0dd5..db4f66b955b6 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"పరిమితం చేసిన సెట్టింగ్ ద్వారా నియంత్రించబడుతుంది"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"కాల్స్ సమయంలో అందుబాటులో ఉండదు"</string>
<string name="disabled" msgid="8017887509554714950">"డిజేబుల్ చేయబడింది"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"అనుమతించినవి"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"అనుమతించబడలేదు"</string>
<string name="install_other_apps" msgid="3232595082023199454">"తెలియని యాప్‌ల ఇన్‌స్టలేషన్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index e360ae6d5278..035644f845f1 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ควบคุมโดยการตั้งค่าที่จำกัด"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"ใช้งานไม่ได้ขณะสนทนาโทรศัพท์"</string>
<string name="disabled" msgid="8017887509554714950">"ปิดอยู่"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"อนุญาต"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"ไม่อนุญาต"</string>
<string name="install_other_apps" msgid="3232595082023199454">"ติดตั้งแอปที่ไม่รู้จัก"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 09f40c761fda..4ef2f436898a 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kinokontrol ng Pinaghihigpitang Setting"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Hindi available habang may tawag"</string>
<string name="disabled" msgid="8017887509554714950">"Naka-disable"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Pinapayagan"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Hindi pinapayagan"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Mag-install ng di-kilalang app"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 19b3baf6c82d..19295aa169ef 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kısıtlanmış ayar tarafından kontrol ediliyor"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Telefon aramaları sırasında kullanılamaz"</string>
<string name="disabled" msgid="8017887509554714950">"Devre dışı"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"İzin verildi"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"İzin verilmiyor"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Bilinmeyen uygulamaları yükleme"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index ff18f5cf10a4..e46137fd237e 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Керується налаштуваннями з обмеженнями"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Недоступно під час викликів"</string>
<string name="disabled" msgid="8017887509554714950">"Вимкнено"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Дозволено"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Заборонено"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Встановлювати невідомі додатки"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index a07a341fd956..72da3784aa4d 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"محدود کردہ ترتیب کے زیر انتظام ہے"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"کالز کے دوران غیر دستیاب"</string>
<string name="disabled" msgid="8017887509554714950">"غیر فعال"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"اجازت ہے"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"اجازت نہیں ہے"</string>
<string name="install_other_apps" msgid="3232595082023199454">"نامعلوم ایپس انسٹال کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 3a10aa801797..795eae45ab92 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Cheklangan sozlama tomonidan boshqariladi"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Chaqiruv vaqtida ishlamaydi"</string>
<string name="disabled" msgid="8017887509554714950">"Oʻchiq"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Ruxsat berilgan"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Ruxsat berilmagan"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Notanish ilovalarni o‘rnatish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 6095e8c2af4c..8f5c0c2675e4 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -109,7 +109,7 @@
<string name="bluetooth_saved_device" msgid="4895871321722311428">"Đã lưu"</string>
<string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Đang hoạt động (chỉ tai trái)"</string>
<string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Đang hoạt động (chỉ tai phải)"</string>
- <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Đang hoạt động (cả tai phải và tai trái)"</string>
+ <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Đang hoạt động (trái và phải)"</string>
<string name="bluetooth_hearing_device_ambient_error" msgid="6035857289108813878">"Không cập nhật được âm lượng xung quanh"</string>
<string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Đang hoạt động (chỉ phát nội dung đa phương tiện). Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string>
<string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Đang hoạt động (chỉ phát nội dung đa phương tiện). Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> pin. Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pin."</string>
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Do chế độ Cài đặt hạn chế kiểm soát"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Không dùng được khi có cuộc gọi"</string>
<string name="disabled" msgid="8017887509554714950">"Đã tắt"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Được phép"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Không được phép"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Cài ứng dụng không rõ nguồn"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 06379e1c254f..85d3c179a623 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由受限设置控制"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"通话期间无法使用"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"允许"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"不允许"</string>
<string name="install_other_apps" msgid="3232595082023199454">"安装未知应用"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index a3ae971b07c4..3ff554000a6c 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由「受限設定」控制"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"通話時無法使用"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"允許"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"不允許"</string>
<string name="install_other_apps" msgid="3232595082023199454">"安裝不明的應用程式"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index f670088de16b..5362d380564f 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由受限制的設定控管"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"通話時無法使用"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"允許"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"不允許"</string>
<string name="install_other_apps" msgid="3232595082023199454">"安裝不明應用程式"</string>
@@ -670,7 +672,7 @@
<string name="add_user_failed" msgid="4809887794313944872">"無法建立新的使用者"</string>
<string name="add_guest_failed" msgid="8074548434469843443">"無法建立新訪客"</string>
<string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
- <string name="edit_user_info_message" msgid="6677556031419002895">"這部裝置的所有使用者都能看到你選擇的名稱和相片。"</string>
+ <string name="edit_user_info_message" msgid="6677556031419002895">"這部裝置的所有使用者,都能看到你選擇的名稱和相片。"</string>
<string name="user_add_user" msgid="7876449291500212468">"新增使用者"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 1d792a0a2864..79ce6d7b25d0 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -539,6 +539,8 @@
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kulawulwe Isethingi Elikhawulelwe"</string>
<string name="disabled_in_phone_call_text" msgid="6568931334337318320">"Akutholakali ngesikhathi samakholi"</string>
<string name="disabled" msgid="8017887509554714950">"Akusebenzi"</string>
+ <!-- no translation found for enabled (3997122818554810678) -->
+ <skip />
<string name="external_source_trusted" msgid="1146522036773132905">"Kuvumelekile"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Akuvumelekile"</string>
<string name="install_other_apps" msgid="3232595082023199454">"Faka ama-app angaziwa"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 91ec83690722..03cb1ffbdef1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1263,6 +1263,8 @@
<!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. -->
<string name="disabled">Disabled</string>
+ <!-- Summary for a settings preference indicating it is enabled [CHAR LIMIT = 30] -->
+ <string name="enabled">Enabled</string>
<!-- Summary of app trusted to install apps [CHAR LIMIT=45] -->
<string name="external_source_trusted">Allowed</string>
<!-- Summary of app not trusted to install apps [CHAR LIMIT=45] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt
index a64e8cc07b15..348941335311 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreferenceBinding.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("ktlint:standard:filename") // remove once we have more bindings
package com.android.settingslib
@@ -29,7 +28,8 @@ interface PrimarySwitchPreferenceBinding : PreferenceBinding {
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
- (preference as PrimarySwitchPreference).apply {
+ // Could bind on PreferenceScreen
+ (preference as? PrimarySwitchPreference)?.apply {
isChecked = preferenceDataStore!!.getBoolean(key, false)
isSwitchEnabled = isEnabled
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 1044750bae25..bf6006b1eddc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -120,7 +120,7 @@ public class RestrictedPreferenceHelper {
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView != null) {
final CharSequence disabledText = getDisabledByAdminSummaryString();
- if (mDisabledByAdmin) {
+ if (mDisabledByAdmin && disabledText != null) {
summaryView.setText(disabledText);
} else if (mDisabledByEcm) {
summaryView.setText(getEcmTextResId());
@@ -132,10 +132,10 @@ public class RestrictedPreferenceHelper {
}
}
- private String getDisabledByAdminSummaryString() {
+ private @Nullable String getDisabledByAdminSummaryString() {
if (isRestrictionEnforcedByAdvancedProtection()) {
- return mContext.getString(com.android.settingslib.widget.restricted
- .R.string.disabled_by_advanced_protection);
+ // Advanced Protection doesn't set the summary string, it keeps the current summary.
+ return null;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
@@ -151,6 +151,18 @@ public class RestrictedPreferenceHelper {
UserHandle.myUserId());
}
+ /**
+ * Configures the user restriction that this preference will track. This is equivalent to
+ * specifying {@link R.styleable#RestrictedPreference_userRestriction} in XML and allows
+ * configuring user restriction at runtime.
+ */
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mAttrUserRestriction = userRestriction == null ||
+ RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, userRestriction,
+ UserHandle.myUserId()) ? null : userRestriction;
+ setDisabledByAdmin(checkRestrictionEnforced());
+ }
+
public void useAdminDisabledSummary(boolean useSummary) {
mDisabledSummary = useSummary;
}
@@ -321,7 +333,10 @@ public class RestrictedPreferenceHelper {
}
if (android.security.Flags.aapmApi() && !isEnabled && mDisabledByAdmin) {
- mPreference.setSummary(getDisabledByAdminSummaryString());
+ String summary = getDisabledByAdminSummaryString();
+ if (summary != null) {
+ mPreference.setSummary(summary);
+ }
}
if (!isEnabled && mDisabledByEcm) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index a5fa6a854e97..67c4207cb8be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -36,6 +36,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
@@ -141,7 +142,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement
final TextView additionalSummaryView = (TextView) holder.findViewById(
R.id.additional_summary);
if (additionalSummaryView != null) {
- if (isDisabledByAdmin()) {
+ if (isDisabledByAdmin() && switchSummary != null) {
additionalSummaryView.setText(switchSummary);
additionalSummaryView.setVisibility(View.VISIBLE);
} else {
@@ -151,7 +152,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement
} else {
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView != null) {
- if (isDisabledByAdmin()) {
+ if (isDisabledByAdmin() && switchSummary != null) {
summaryView.setText(switchSummary);
summaryView.setVisibility(View.VISIBLE);
}
@@ -171,14 +172,10 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement
() -> context.getString(resId));
}
- private String getRestrictedSwitchSummary() {
+ private @Nullable String getRestrictedSwitchSummary() {
if (mHelper.isRestrictionEnforcedByAdvancedProtection()) {
- final int apmResId = isChecked()
- ? com.android.settingslib.widget.restricted.R.string
- .enabled_by_advanced_protection
- : com.android.settingslib.widget.restricted.R.string
- .disabled_by_advanced_protection;
- return getContext().getString(apmResId);
+ // Advanced Protection doesn't set the summary string, it keeps the current summary.
+ return null;
}
return isChecked()
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt
new file mode 100644
index 000000000000..b52a9017fc16
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+/**
+ * BatteryLevelsInfo contains the battery levels of different components of a bluetooth device.
+ * The range of a valid battery level is [0-100], and -1 if the battery level is not applicable.
+ */
+data class BatteryLevelsInfo(
+ val leftBatteryLevel: Int,
+ val rightBatteryLevel: Int,
+ val caseBatteryLevel: Int,
+ val overallBatteryLevel: Int,
+) \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index e78a69239334..ae9ad958b287 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -51,6 +51,8 @@ import com.android.settingslib.widget.AdaptiveOutlineDrawable;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -1268,4 +1270,15 @@ public class BluetoothUtils {
return false;
}
+
+ /** Gets key missing count of the device. This is a workaround before the API is rolled out. */
+ public static Integer getKeyMissingCount(BluetoothDevice device) {
+ try {
+ Method m = BluetoothDevice.class.getDeclaredMethod("getKeyMissingCount");
+ return (int) m.invoke(device);
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ Log.w(TAG, "error happens when getKeyMissingCount.");
+ return null;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index bb96041739eb..5f88bcd8d65d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -44,10 +44,12 @@ import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
+import android.view.InputDevice;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.R;
@@ -62,6 +64,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -150,6 +153,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
private boolean mIsHearingAidProfileConnectedFail = false;
private boolean mIsLeAudioProfileConnectedFail = false;
private boolean mUnpairing;
+ @Nullable
+ private final InputDevice mInputDevice;
+ private final boolean mIsDeviceStylus;
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
@@ -193,6 +199,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
initDrawableCache();
mUnpairing = false;
+ mInputDevice = BluetoothUtils.getInputDevice(mContext, getAddress());
+ mIsDeviceStylus = BluetoothUtils.isDeviceStylus(mInputDevice, this);
}
/** Clears any pending messages in the message queue. */
@@ -1622,6 +1630,86 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
}
+ /**
+ * Returns the battery levels of all components of the bluetooth device. If no battery info is
+ * available then returns null.
+ */
+ @WorkerThread
+ @Nullable
+ public BatteryLevelsInfo getBatteryLevelsInfo() {
+ // Try getting the battery information from metadata.
+ BatteryLevelsInfo metadataSourceBattery = getBatteryFromMetadata();
+ if (metadataSourceBattery != null) {
+ return metadataSourceBattery;
+ }
+ // Get the battery information from Bluetooth service.
+ return getBatteryFromBluetoothService();
+ }
+
+ @Nullable
+ private BatteryLevelsInfo getBatteryFromMetadata() {
+ if (BluetoothUtils.getBooleanMetaData(mDevice,
+ BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ // The device is untethered headset, containing both earbuds and case.
+ int leftBattery =
+ BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+ int rightBattery =
+ BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+ int caseBattery =
+ BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY);
+
+ if (leftBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ && rightBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ && caseBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ Log.d(TAG, "No battery info from metadata is available for untethered device "
+ + mDevice.getAnonymizedAddress());
+ return null;
+ } else {
+ int overallBattery =
+ Arrays.stream(new int[]{leftBattery, rightBattery, caseBattery})
+ .filter(battery -> battery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .min()
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ Log.d(TAG, "Acquired battery info from metadata for untethered device "
+ + mDevice.getAnonymizedAddress()
+ + " left earbud battery: " + leftBattery
+ + " right earbud battery: " + rightBattery
+ + " case battery: " + caseBattery
+ + " overall battery: " + overallBattery);
+ return new BatteryLevelsInfo(
+ leftBattery, rightBattery, caseBattery, overallBattery);
+ }
+ } else if (mInputDevice != null || mIsDeviceStylus) {
+ // The device is input device, using METADATA_MAIN_BATTERY field to get battery info.
+ int overallBattery = BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_MAIN_BATTERY);
+ if (overallBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ Log.d(TAG, "No battery info from metadata is available for input device "
+ + mDevice.getAnonymizedAddress());
+ return null;
+ } else {
+ Log.d(TAG, "Acquired battery info from metadata for input device "
+ + mDevice.getAnonymizedAddress()
+ + " overall battery: " + overallBattery);
+ return new BatteryLevelsInfo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ overallBattery);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private BatteryLevelsInfo getBatteryFromBluetoothService() {
+ // TODO(b/397847825): Implement the logic to get battery from Bluetooth service.
+ return null;
+ }
+
private CharSequence getTvBatterySummary(int mainBattery, int leftBattery, int rightBattery,
int lowBatteryColorRes) {
// Since there doesn't seem to be a way to use format strings to add the
@@ -1775,10 +1863,31 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
+ " mIsLeAudioProfileConnectedFail=" + mIsLeAudioProfileConnectedFail
+ " mIsHeadsetProfileConnectedFail=" + mIsHeadsetProfileConnectedFail
+ " isConnectedSapDevice()=" + isConnectedSapDevice());
-
- return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail
- || (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail)
- || mIsLeAudioProfileConnectedFail;
+ if (mIsA2dpProfileConnectedFail) {
+ A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ if (a2dpProfile != null && a2dpProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ if (mIsHearingAidProfileConnectedFail) {
+ HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
+ if (hearingAidProfile != null && hearingAidProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ if (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail) {
+ HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile();
+ if (headsetProfile != null && headsetProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ if (mIsLeAudioProfileConnectedFail) {
+ LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile();
+ if (leAudioProfile != null && leAudioProfile.isEnabled(mDevice)) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 08f7806207db..01d8694256f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -89,11 +89,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
public static final String ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED =
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_DEVICE_CONNECTED";
+ public static final String ACTION_LE_AUDIO_PRIVATE_BROADCAST_RECEIVED =
+ "com.android.settings.action.BLUETOOTH_LE_AUDIO_PRIVATE_BROADCAST_RECEIVED";
public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE";
public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING";
public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING";
+ public static final String EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA = "RECEIVE_DATA";
public static final String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID =
"bluetooth_le_broadcast_primary_device_group_id";
public static final int BROADCAST_STATE_UNKNOWN = 0;
@@ -721,7 +724,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "The BluetoothLeBroadcast is null");
return null;
}
- if (mBluetoothLeBroadcastMetadata == null) {
+ if (mBluetoothLeBroadcastMetadata == null
+ // mBroadcastId is updated when onBroadcastStarted, which is always before
+ // onBroadcastMetadataChanged, so mBroadcastId is always the latest broadcast info
+ || mBluetoothLeBroadcastMetadata.getBroadcastId() != mBroadcastId) {
final List<BluetoothLeBroadcastMetadata> metadataList =
mServiceBroadcast.getAllBroadcastMetadata();
mBluetoothLeBroadcastMetadata =
@@ -729,6 +735,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
.filter(i -> i.getBroadcastId() == mBroadcastId)
.findFirst()
.orElse(null);
+ Log.d(TAG, "getLatestBluetoothLeBroadcastMetadata for broadcast id " + mBroadcastId);
}
return mBluetoothLeBroadcastMetadata;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt
new file mode 100644
index 000000000000..a284d2010195
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveData.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothDevice
+import android.os.Parcel
+import android.os.Parcelable
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED
+
+/**
+ * Data class representing information received in a private broadcast.
+ * This class encapsulates details about the sink device, source ID, broadcast ID, and the
+ * broadcast source state.
+ *
+ * @param sink The [BluetoothDevice] acting as the sink.
+ * @param sourceId The ID of the audio source.
+ * @param broadcastId The ID of the broadcast source.
+ * @param programInfo The program info string of the broadcast source.
+ * @param state The current state of the broadcast source.
+ */
+data class PrivateBroadcastReceiveData(
+ val sink: BluetoothDevice?,
+ val sourceId: Int = -1,
+ val broadcastId: Int = -1,
+ val programInfo: String = "",
+ val state: LocalBluetoothLeBroadcastSourceState?,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(sink, flags)
+ parcel.writeInt(sourceId)
+ parcel.writeInt(broadcastId)
+ parcel.writeString(programInfo)
+ parcel.writeSerializable(state)
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<PrivateBroadcastReceiveData> =
+ object : Parcelable.Creator<PrivateBroadcastReceiveData> {
+ override fun createFromParcel(parcel: Parcel) =
+ parcel.run {
+ PrivateBroadcastReceiveData(
+ sink = readParcelable(
+ BluetoothDevice::class.java.classLoader,
+ BluetoothDevice::class.java
+ ),
+ sourceId = readInt(),
+ broadcastId = readInt(),
+ programInfo = readString() ?: "",
+ state = readSerializable(
+ LocalBluetoothLeBroadcastSourceState::class.java.classLoader,
+ LocalBluetoothLeBroadcastSourceState::class.java
+ )
+ )
+ }
+ override fun newArray(size: Int): Array<PrivateBroadcastReceiveData?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ fun PrivateBroadcastReceiveData.isValid(): Boolean {
+ return sink != null
+ && sourceId != -1
+ && broadcastId != -1
+ && (state == STREAMING
+ || state == PAUSED
+ || state == DECRYPTION_FAILED)
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt
new file mode 100644
index 000000000000..92455c05e3d7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.supervision
+
+/** Constants used in supervision logs. */
+object SupervisionLog {
+ const val TAG = "SupervisionSettings"
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt
new file mode 100644
index 000000000000..1be8a17915a1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/supervision/SupervisionRestrictionsHelper.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.supervision
+
+import android.app.admin.DeviceAdminReceiver
+import android.app.supervision.SupervisionManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.util.Log
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+
+/** Helper class for supervision-enforced restrictions. */
+object SupervisionRestrictionsHelper {
+
+ /**
+ * Creates an instance of [EnforcedAdmin] that uses the correct supervision component or returns
+ * null if supervision is not enabled.
+ */
+ @JvmStatic
+ fun createEnforcedAdmin(
+ context: Context,
+ restriction: String,
+ user: UserHandle,
+ ): EnforcedAdmin? {
+ val supervisionManager = context.getSystemService(SupervisionManager::class.java)
+ val supervisionAppPackage = supervisionManager?.activeSupervisionAppPackage ?: return null
+ var supervisionComponent: ComponentName? = null
+
+ // Try to find the service whose package matches the active supervision app.
+ val resolveSupervisionApps =
+ context.packageManager.queryIntentServicesAsUser(
+ Intent("android.app.action.BIND_SUPERVISION_APP_SERVICE"),
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ user.identifier,
+ )
+ resolveSupervisionApps
+ .mapNotNull { it.serviceInfo?.componentName }
+ .find { it.packageName == supervisionAppPackage }
+ ?.let { supervisionComponent = it }
+
+ if (supervisionComponent == null) {
+ // Try to find the PO receiver whose package matches the active supervision app, for
+ // backwards compatibility.
+ val resolveDeviceAdmins =
+ context.packageManager.queryBroadcastReceiversAsUser(
+ Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ user.identifier,
+ )
+ resolveDeviceAdmins
+ .mapNotNull { it.activityInfo?.componentName }
+ .find { it.packageName == supervisionAppPackage }
+ ?.let { supervisionComponent = it }
+ }
+
+ if (supervisionComponent == null) {
+ Log.d(SupervisionLog.TAG, "Could not find the supervision component.")
+ }
+ return EnforcedAdmin(supervisionComponent, restriction, user)
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
index c5e6f60e3fa6..2b95fd1cdd9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java
@@ -93,18 +93,10 @@ public class CreateUserActivity extends Activity {
@Override
public boolean onTouchEvent(@Nullable MotionEvent event) {
- onBackInvoked();
+ cancel();
return super.onTouchEvent(event);
}
- private void onBackInvoked() {
- if (mSetupUserDialog != null) {
- mSetupUserDialog.dismiss();
- }
- setResult(RESULT_CANCELED);
- finish();
- }
-
@VisibleForTesting
void setSuccessResult(String userName, Drawable userIcon, String path, Boolean isAdmin) {
Intent intent = new Intent(this, CreateUserActivity.class);
@@ -112,14 +104,12 @@ public class CreateUserActivity extends Activity {
intent.putExtra(EXTRA_IS_ADMIN, isAdmin);
intent.putExtra(EXTRA_USER_ICON_PATH, path);
- mSetupUserDialog.dismiss();
setResult(RESULT_OK, intent);
finish();
}
@VisibleForTesting
void cancel() {
- mSetupUserDialog.dismiss();
setResult(RESULT_CANCELED);
finish();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index d9f1b632323c..bce229f5a13d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -282,7 +282,7 @@ public class CreateUserDialogController {
mCustomDialogHelper.getDialog().dismiss();
break;
case EXIT_DIALOG:
- finish();
+ mUserCreationDialog.dismiss();
break;
default:
if (mCurrentState < EXIT_DIALOG) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 88bccc9f6ebd..2ed437c94439 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -509,6 +509,7 @@ open class WifiUtils {
val intent = AdvancedProtectionManager.createSupportIntent(
AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP,
AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION)
+ intent.putExtra(DIALOG_WINDOW_TYPE, dialogWindowType)
onStartActivity(intent)
} else if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) {
onAllowed()
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt
new file mode 100644
index 000000000000..78dba57028ba
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.res.Resources
+import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceStateAutoRotateSettingManagerImplTest {
+ @get:Rule
+ val rule = MockitoJUnit.rule()
+
+ private val fakeSecureSettings = FakeSecureSettings()
+ private val executor: Executor = Executor { it.run() }
+ private val configPerDeviceStateRotationLockDefaults = arrayOf(
+ "$DEVICE_STATE_ROTATION_KEY_HALF_FOLDED:" +
+ "$DEVICE_STATE_ROTATION_LOCK_IGNORED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED",
+ "$DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY:" +
+ "$DEVICE_STATE_ROTATION_LOCK_IGNORED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED",
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED",
+ "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED",
+ )
+
+ @Mock
+ private lateinit var mockContext: Context
+
+ @Mock
+ private lateinit var mockContentResolver: ContentResolver
+
+ @Mock
+ private lateinit var mockPosturesHelper: PosturesHelper
+
+ @Mock
+ private lateinit var mockHandler: Handler
+
+ @Mock
+ private lateinit var mockDeviceStateManager: DeviceStateManager
+
+ @Mock
+ private lateinit var mockResources: Resources
+ private lateinit var settingManager: DeviceStateAutoRotateSettingManagerImpl
+
+ @Before
+ fun setUp() {
+ whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+ whenever(mockContext.resources).thenReturn(mockResources)
+ whenever(mockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
+ .thenReturn(configPerDeviceStateRotationLockDefaults)
+ whenever(mockHandler.post(any(Runnable::class.java))).thenAnswer { invocation ->
+ val runnable = invocation.arguments[0] as Runnable
+ runnable.run()
+ null
+ }
+ whenever(mockContext.getSystemService(DeviceStateManager::class.java))
+ .thenReturn(mockDeviceStateManager)
+ whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_UNFOLDED))
+ .thenReturn(DEVICE_STATE_ROTATION_KEY_UNFOLDED)
+ whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_FOLDED))
+ .thenReturn(DEVICE_STATE_ROTATION_KEY_FOLDED)
+ whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_HALF_FOLDED))
+ .thenReturn(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED)
+ whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_INVALID))
+ .thenReturn(DEVICE_STATE_ROTATION_LOCK_IGNORED)
+ whenever(mockPosturesHelper.deviceStateToPosture(DEVICE_STATE_REAR_DISPLAY))
+ .thenReturn(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY)
+
+ settingManager =
+ DeviceStateAutoRotateSettingManagerImpl(
+ mockContext,
+ executor,
+ fakeSecureSettings,
+ mockHandler,
+ mockPosturesHelper,
+ )
+ }
+
+ @Test
+ fun registerListener_onSettingsChanged_listenerNotified() {
+ val listener = mock(DeviceStateAutoRotateSettingListener::class.java)
+ settingManager.registerListener(listener)
+
+ persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+
+ verify(listener).onSettingsChanged()
+ }
+
+ @Test
+ fun registerMultipleListeners_onSettingsChanged_allListenersNotified() {
+ val listener1 = mock(DeviceStateAutoRotateSettingListener::class.java)
+ val listener2 = mock(DeviceStateAutoRotateSettingListener::class.java)
+ settingManager.registerListener(listener1)
+ settingManager.registerListener(listener2)
+
+ persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+
+ verify(listener1).onSettingsChanged()
+ verify(listener2).onSettingsChanged()
+ }
+
+ @Test
+ fun unregisterListener_onSettingsChanged_listenerNotNotified() {
+ val listener = mock(DeviceStateAutoRotateSettingListener::class.java)
+ settingManager.registerListener(listener)
+ settingManager.unregisterListener(listener)
+
+ persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+
+ verify(listener, never()).onSettingsChanged()
+ }
+
+ @Test
+ fun getAutoRotateSetting_offForUnfolded_returnsOff() {
+ persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+
+ val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_UNFOLDED)
+
+ assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_LOCKED)
+ }
+
+ @Test
+ fun getAutoRotateSetting_onForFolded_returnsOn() {
+ persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED)
+
+ val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED)
+
+ assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED)
+ }
+
+ @Test
+ fun getAutoRotateSetting_forInvalidPostureWithNoFallback_returnsIgnored() {
+ val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_INVALID)
+
+ assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED)
+ }
+
+ @Test
+ fun getAutoRotateSetting_forInvalidPosture_returnsSettingForFallbackPosture() {
+ persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED)
+ persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+
+ val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_HALF_FOLDED)
+
+ assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED)
+ }
+
+ @Test
+ fun getAutoRotateSetting_invalidFormat_returnsIgnored() {
+ persistSettings("invalid_format")
+
+ val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED)
+
+ assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED)
+ }
+
+ @Test
+ fun getAutoRotateSetting_invalidNumberFormat_returnsIgnored() {
+ persistSettings("$DEVICE_STATE_ROTATION_KEY_FOLDED:4")
+
+ val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED)
+
+ assertThat(autoRotateSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_IGNORED)
+ }
+
+ @Test
+ fun getAutoRotateSetting_multipleSettings_returnsCorrectSetting() {
+ persistSettings(
+ "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED"
+ )
+
+ val foldedSetting = settingManager.getRotationLockSetting(DEVICE_STATE_FOLDED)
+ val unfoldedSetting = settingManager.getRotationLockSetting(DEVICE_STATE_UNFOLDED)
+
+ assertThat(foldedSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_LOCKED)
+ assertThat(unfoldedSetting).isEqualTo(DEVICE_STATE_ROTATION_LOCK_UNLOCKED)
+ }
+
+ @Test
+ fun isAutoRotateOff_offForUnfolded_returnsTrue() {
+ persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+
+ val isAutoRotateOff = settingManager.isRotationLocked(DEVICE_STATE_UNFOLDED)
+
+ assertThat(isAutoRotateOff).isTrue()
+ }
+
+ @Test
+ fun isRotationLockedForAllStates_allStatesLocked_returnsTrue() {
+ persistSettings(
+ "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED"
+ )
+
+ val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates()
+
+ assertThat(isRotationLockedForAllStates).isTrue()
+ }
+
+ @Test
+ fun isRotationLockedForAllStates_someStatesLocked_returnsFalse() {
+ persistSettings(
+ "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED"
+ )
+
+ val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates()
+
+ assertThat(isRotationLockedForAllStates).isFalse()
+ }
+
+ @Test
+ fun isRotationLockedForAllStates_noStatesLocked_returnsFalse() {
+ persistSettings(
+ "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED"
+ )
+
+ val isRotationLockedForAllStates = settingManager.isRotationLockedForAllStates()
+
+ assertThat(isRotationLockedForAllStates).isFalse()
+ }
+
+ @Test
+ fun getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() {
+ val settableDeviceStates = settingManager.getSettableDeviceStates()
+
+ assertThat(settableDeviceStates)
+ .containsExactly(
+ SettableDeviceState(DEVICE_STATE_ROTATION_KEY_UNFOLDED, isSettable = true),
+ SettableDeviceState(DEVICE_STATE_ROTATION_KEY_FOLDED, isSettable = true),
+ SettableDeviceState(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED, isSettable = false),
+ SettableDeviceState(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY, isSettable = false),
+ SettableDeviceState(DEVICE_STATE_ROTATION_LOCK_IGNORED, isSettable = false),
+ )
+ }
+
+ private fun persistSettings(devicePosture: Int, autoRotateSetting: Int) {
+ persistSettings("$devicePosture:$autoRotateSetting")
+ }
+
+ private fun persistSettings(value: String) {
+ fakeSecureSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK, value, UserHandle.USER_CURRENT
+ )
+ }
+
+ private companion object {
+ const val DEVICE_STATE_FOLDED = 0
+ const val DEVICE_STATE_HALF_FOLDED = 1
+ const val DEVICE_STATE_UNFOLDED = 2
+ const val DEVICE_STATE_REAR_DISPLAY = 3
+ const val DEVICE_STATE_INVALID = 4
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
index 9f9aaf5ff83a..baebaf7dfef0 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -40,7 +40,6 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
-import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
import com.google.common.truth.Expect;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
index 08484bceb1fb..8d9982fe7210 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
@@ -2,9 +2,9 @@ package com.android.settingslib.graph;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java
index 9ab17c49bbec..b849919cb389 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppCopyingHelperTest.java
@@ -18,10 +18,10 @@ package com.android.settingslib.users;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
index ab28d061419c..94bb61614411 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
@@ -16,11 +16,11 @@
package com.android.settingslib.users;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.nullable;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
index 995314e44767..055487b79f78 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java
@@ -22,7 +22,7 @@ import static com.android.settingslib.avatarpicker.AvatarPhotoController.REQUEST
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index e7533bc1044b..7f4d366b76d4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -71,7 +71,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -197,7 +197,7 @@ public class WifiTrackerTest {
.registerNetworkScoreCache(
anyInt(),
mScoreCacheCaptor.capture(),
- Matchers.anyInt());
+ ArgumentMatchers.anyInt());
// Capture requested keys and count down latch if present
doAnswer(
@@ -213,7 +213,7 @@ public class WifiTrackerTest {
}
return true;
}
- }).when(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
+ }).when(mockNetworkScoreManager).requestScores(ArgumentMatchers.<NetworkKey[]>any());
// We use a latch to detect callbacks as Tracker initialization state often invokes
// callbacks
@@ -464,9 +464,9 @@ public class WifiTrackerTest {
startTracking(tracker);
verify(mockNetworkScoreManager)
.registerNetworkScoreCache(
- Matchers.anyInt(),
+ ArgumentMatchers.anyInt(),
mScoreCacheCaptor.capture(),
- Matchers.anyInt());
+ ArgumentMatchers.anyInt());
WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index dbbbd5bf8089..f9769fa61e0d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.atMostOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -130,7 +131,7 @@ public class RestrictedPreferenceHelperTest {
@RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
@Test
- public void bindPreference_disabled_byAdvancedProtection_shouldDisplayDisabledSummary() {
+ public void bindPreference_disabled_byAdvancedProtection_shouldKeepExistingSummary() {
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
final RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils
@@ -143,16 +144,14 @@ public class RestrictedPreferenceHelperTest {
.thenReturn(summaryView);
when(mDevicePolicyManager.getEnforcingAdmin(UserHandle.myUserId(), userRestriction))
.thenReturn(advancedProtectionEnforcingAdmin);
- when(mContext.getString(
- com.android.settingslib.widget.restricted.R.string.disabled_by_advanced_protection))
- .thenReturn("advanced_protection");
+ summaryView.setText("existing summary");
mHelper.useAdminDisabledSummary(true);
mHelper.setDisabledByAdmin(enforcedAdmin);
mHelper.onBindViewHolder(mViewHolder);
- verify(summaryView).setText("advanced_protection");
- verify(summaryView, never()).setVisibility(View.GONE);
+ verify(summaryView, atMostOnce()).setText(any()); // To set it to existing summary
+ verify(summaryView, never()).setVisibility(View.VISIBLE);
}
@RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index f6e26a7200ef..f57ee0c0930e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -40,12 +40,14 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
+import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import android.util.LruCache;
+import android.view.InputDevice;
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
@@ -77,8 +79,10 @@ public class CachedBluetoothDeviceTest {
private static final String DEVICE_ALIAS_NEW = "TestAliasNew";
private static final String TWS_BATTERY_LEFT = "15";
private static final String TWS_BATTERY_RIGHT = "25";
+ private static final String TWS_BATTERY_CASE = "10";
private static final String TWS_LOW_BATTERY_THRESHOLD_LOW = "10";
private static final String TWS_LOW_BATTERY_THRESHOLD_HIGH = "25";
+ private static final String MAIN_BATTERY = "80";
private static final String TEMP_BOND_METADATA =
"<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private static final short RSSI_1 = 10;
@@ -87,6 +91,8 @@ public class CachedBluetoothDeviceTest {
private static final boolean JUSTDISCOVERED_2 = false;
private static final int LOW_BATTERY_COLOR = android.R.color.holo_red_dark;
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+ private static final int TEST_DEVICE_ID = 123;
+ private final InputDevice mInputDevice = mock(InputDevice.class);
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
@@ -116,6 +122,8 @@ public class CachedBluetoothDeviceTest {
private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock
private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
+ @Mock
+ private InputManager mInputManager;
private CachedBluetoothDevice mCachedDevice;
private CachedBluetoothDevice mSubCachedDevice;
private AudioManager mAudioManager;
@@ -174,6 +182,8 @@ public class CachedBluetoothDeviceTest {
updateProfileStatus(connectingProfile, BluetoothProfile.STATE_CONNECTING);
// Set connection policy
when(connectingProfile.getConnectionPolicy(mDevice)).thenReturn(connectionPolicy);
+ when(connectingProfile.isEnabled(mDevice))
+ .thenReturn(connectionPolicy > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Act & Assert:
// Get the expected connection summary.
@@ -183,6 +193,9 @@ public class CachedBluetoothDeviceTest {
@Test
public void onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem() {
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
String connectTimeoutString = mContext.getString(R.string.profile_connect_timeout_subtext);
testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
@@ -197,6 +210,9 @@ public class CachedBluetoothDeviceTest {
@Test
public void onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem() {
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
@@ -209,6 +225,9 @@ public class CachedBluetoothDeviceTest {
@Test
public void onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem() {
+ when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
@@ -1822,6 +1841,10 @@ public class CachedBluetoothDeviceTest {
@Test
public void getConnectionSummary_profileConnectedFail_showErrorMessage() {
final A2dpProfile profile = mock(A2dpProfile.class);
+ when(mProfileManager.getA2dpProfile()).thenReturn(profile);
+ when(profile.getConnectionPolicy(mDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(profile.isEnabled(mDevice)).thenReturn(true);
mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
@@ -1834,6 +1857,10 @@ public class CachedBluetoothDeviceTest {
@Test
public void getTvConnectionSummary_profileConnectedFail_showErrorMessage() {
final A2dpProfile profile = mock(A2dpProfile.class);
+ when(mProfileManager.getA2dpProfile()).thenReturn(profile);
+ when(profile.getConnectionPolicy(mDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(profile.isEnabled(mDevice)).thenReturn(true);
mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
@@ -2175,6 +2202,74 @@ public class CachedBluetoothDeviceTest {
assertThat(mCachedDevice.isHearingDevice()).isFalse();
}
+ @Test
+ public void getBatteryLevelsInfo_untetheredHeadsetWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "true".getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
+ TWS_BATTERY_LEFT.getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
+ TWS_BATTERY_RIGHT.getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
+ TWS_BATTERY_CASE.getBytes());
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_LEFT));
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_RIGHT));
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_CASE));
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_CASE));
+ }
+
+ @Test
+ public void getBatteryLevelsInfo_inputDeviceWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "false".getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
+ MAIN_BATTERY.getBytes());
+ when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
+ when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{TEST_DEVICE_ID});
+ when(mInputManager.getInputDeviceBluetoothAddress(TEST_DEVICE_ID)).thenReturn(
+ DEVICE_ADDRESS);
+ when(mInputManager.getInputDevice(TEST_DEVICE_ID)).thenReturn(mInputDevice);
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(MAIN_BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevelsInfo_stylusDeviceWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "false".getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+ BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
+ MAIN_BATTERY.getBytes());
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(MAIN_BATTERY));
+ }
+
private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
doReturn(status).when(profile).getConnectionStatus(mDevice);
mCachedDevice.onProfileStateChanged(profile, status);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt
new file mode 100644
index 000000000000..5fd67a16a305
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PrivateBroadcastReceiveDataTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.os.Parcel
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState
+import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData.Companion.isValid
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class PrivateBroadcastReceiveDataTest {
+
+ @Test
+ fun parcelable() {
+ val original = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = 2,
+ programInfo = "Test Program",
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+
+ val parcel = Parcel.obtain()
+ original.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ val recreated = PrivateBroadcastReceiveData.CREATOR.createFromParcel(parcel)
+
+ assertEquals(original, recreated)
+ }
+
+ @Test
+ fun isValid_validData() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = 2,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertTrue(data.isValid())
+ }
+
+ @Test
+ fun isValid_nullSink() {
+ val data = PrivateBroadcastReceiveData(
+ sink = null,
+ sourceId = 1,
+ broadcastId = 2,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_invalidSourceId() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = -1,
+ broadcastId = 2,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_invalidBroadcastId() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = -1,
+ state = LocalBluetoothLeBroadcastSourceState.STREAMING
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_nullState() {
+ val data = PrivateBroadcastReceiveData(
+ sink = sink,
+ sourceId = 1,
+ broadcastId = 2,
+ state = null
+ )
+ assertFalse(data.isValid())
+ }
+
+ @Test
+ fun isValid_correctStates() {
+ assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.STREAMING).isValid())
+ assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.PAUSED).isValid())
+ assertTrue(PrivateBroadcastReceiveData(sink, 1, 1, state = LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED).isValid())
+ }
+
+ private companion object {
+ const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+
+ val sink: BluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(
+ TEST_DEVICE_ADDRESS,
+ BluetoothDevice.ADDRESS_TYPE_RANDOM
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 3d16d6f1cd56..c387d48b33da 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -507,4 +507,25 @@ public class MediaDeviceTest {
assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo(
SELECTION_BEHAVIOR_TRANSFER);
}
+
+ @Test
+ public void getSelectionBehavior_withRouteListingPreferenceItem_returnPreferenceBehavior() {
+ mItem =
+ new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1)
+ .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP)
+ .build();
+ MediaDevice castMediaDevice = new ComplexMediaDevice(mContext, mRouteInfo1, mItem);
+
+ assertThat(castMediaDevice.hasRouteListingPreferenceItem()).isTrue();
+ assertThat(castMediaDevice.getSelectionBehavior()).isEqualTo(SELECTION_BEHAVIOR_GO_TO_APP);
+ }
+
+ @Test
+ public void getSelectionBehavior_withoutRouteListingPreferenceItem_returnTransfer() {
+ MediaDevice castMediaDevice =
+ new ComplexMediaDevice(mContext, mRouteInfo1, /* item= */ null);
+
+ assertThat(castMediaDevice.hasRouteListingPreferenceItem()).isFalse();
+ assertThat(castMediaDevice.getSelectionBehavior()).isEqualTo(SELECTION_BEHAVIOR_TRANSFER);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS
new file mode 100644
index 000000000000..04e7058b4384
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/core/java/android/app/supervision/OWNERS
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt
index 2ceed2875cb4..83ffa9399dc0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionIntentProviderTest.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.supervision
import android.app.supervision.SupervisionManager
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -77,8 +78,8 @@ class SupervisionIntentProviderTest {
fun getSettingsIntent_unresolvedIntent() {
`when`(mockSupervisionManager.activeSupervisionAppPackage)
.thenReturn(SUPERVISION_APP_PACKAGE)
- `when`(mockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
- .thenReturn(emptyList())
+ `when`(mockPackageManager.queryIntentActivitiesAsUser(any<Intent>(), anyInt(), anyInt()))
+ .thenReturn(emptyList<ResolveInfo>())
val intent = SupervisionIntentProvider.getSettingsIntent(context)
@@ -89,7 +90,7 @@ class SupervisionIntentProviderTest {
fun getSettingsIntent_resolvedIntent() {
`when`(mockSupervisionManager.activeSupervisionAppPackage)
.thenReturn(SUPERVISION_APP_PACKAGE)
- `when`(mockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+ `when`(mockPackageManager.queryIntentActivitiesAsUser(any<Intent>(), anyInt(), anyInt()))
.thenReturn(listOf(ResolveInfo()))
val intent = SupervisionIntentProvider.getSettingsIntent(context)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt
new file mode 100644
index 000000000000..872fc2a44b3d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/supervision/SupervisionRestrictionsHelperTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.supervision
+
+import android.app.admin.DeviceAdminReceiver
+import android.app.supervision.SupervisionManager
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+/**
+ * Unit tests for [SupervisionRestrictionsHelper].
+ *
+ * Run with `atest SupervisionRestrictionsHelperTest`.
+ */
+@RunWith(AndroidJUnit4::class)
+class SupervisionRestrictionsHelperTest {
+ @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var mockPackageManager: PackageManager
+
+ @Mock private lateinit var mockSupervisionManager: SupervisionManager
+
+ private lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ context =
+ object : ContextWrapper(InstrumentationRegistry.getInstrumentation().context) {
+ override fun getPackageManager() = mockPackageManager
+
+ override fun getSystemService(name: String) =
+ when (name) {
+ Context.SUPERVISION_SERVICE -> mockSupervisionManager
+ else -> super.getSystemService(name)
+ }
+ }
+ }
+
+ @Test
+ fun createEnforcedAdmin_nullSupervisionPackage() {
+ `when`(mockSupervisionManager.activeSupervisionAppPackage).thenReturn(null)
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNull()
+ }
+
+ @Test
+ fun createEnforcedAdmin_supervisionAppService() {
+ val resolveInfo =
+ ResolveInfo().apply {
+ serviceInfo =
+ ServiceInfo().apply {
+ packageName = SUPERVISION_APP_PACKAGE
+ name = "service.class"
+ }
+ }
+
+ `when`(mockSupervisionManager.activeSupervisionAppPackage)
+ .thenReturn(SUPERVISION_APP_PACKAGE)
+ `when`(
+ mockPackageManager.queryIntentServicesAsUser(
+ argThat(hasAction("android.app.action.BIND_SUPERVISION_APP_SERVICE")),
+ anyInt(),
+ eq(USER_ID),
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNotNull()
+ assertThat(enforcedAdmin!!.component).isEqualTo(resolveInfo.serviceInfo.componentName)
+ assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(RESTRICTION)
+ assertThat(enforcedAdmin.user).isEqualTo(USER_HANDLE)
+ }
+
+ @Test
+ fun createEnforcedAdmin_profileOwnerReceiver() {
+ val resolveInfo =
+ ResolveInfo().apply {
+ activityInfo =
+ ActivityInfo().apply {
+ packageName = SUPERVISION_APP_PACKAGE
+ name = "service.class"
+ }
+ }
+
+ `when`(mockSupervisionManager.activeSupervisionAppPackage)
+ .thenReturn(SUPERVISION_APP_PACKAGE)
+ `when`(mockPackageManager.queryIntentServicesAsUser(any<Intent>(), anyInt(), eq(USER_ID)))
+ .thenReturn(emptyList<ResolveInfo>())
+ `when`(
+ mockPackageManager.queryBroadcastReceiversAsUser(
+ argThat(hasAction(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED)),
+ anyInt(),
+ eq(USER_ID),
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNotNull()
+ assertThat(enforcedAdmin!!.component).isEqualTo(resolveInfo.activityInfo.componentName)
+ assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(RESTRICTION)
+ assertThat(enforcedAdmin.user).isEqualTo(USER_HANDLE)
+ }
+
+ @Test
+ fun createEnforcedAdmin_noSupervisionComponent() {
+ `when`(mockSupervisionManager.activeSupervisionAppPackage)
+ .thenReturn(SUPERVISION_APP_PACKAGE)
+ `when`(mockPackageManager.queryIntentServicesAsUser(any<Intent>(), anyInt(), anyInt()))
+ .thenReturn(emptyList<ResolveInfo>())
+ `when`(mockPackageManager.queryBroadcastReceiversAsUser(any<Intent>(), anyInt(), anyInt()))
+ .thenReturn(emptyList<ResolveInfo>())
+
+ val enforcedAdmin =
+ SupervisionRestrictionsHelper.createEnforcedAdmin(context, RESTRICTION, USER_HANDLE)
+
+ assertThat(enforcedAdmin).isNotNull()
+ assertThat(enforcedAdmin!!.component).isNull()
+ assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(RESTRICTION)
+ assertThat(enforcedAdmin.user).isEqualTo(USER_HANDLE)
+ }
+
+ private fun hasAction(action: String) =
+ object : ArgumentMatcher<Intent> {
+ override fun matches(intent: Intent?) = intent?.action == action
+ }
+
+ private companion object {
+ const val SUPERVISION_APP_PACKAGE = "app.supervision"
+ const val RESTRICTION = "restriction"
+ val USER_HANDLE = UserHandle.CURRENT
+ val USER_ID = USER_HANDLE.identifier
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
index f58eb7cc2e31..220c03e53be2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java
@@ -65,23 +65,23 @@ public class CreateUserActivityTest {
}
@Test
- public void onTouchEvent_dismissesDialogAndCancelsResult() {
+ public void onTouchEvent_finishesActivityAndCancelsResult() {
mCreateUserActivity.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0,
0));
- assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(mCreateUserActivity.isFinishing()).isTrue();
assertThat(shadowOf(mCreateUserActivity).getResultCode())
.isEqualTo(Activity.RESULT_CANCELED);
}
@Test
- public void setSuccessResult_dismissesDialogAndSetsSuccessResult() {
+ public void setSuccessResult_finishesActivityAndSetsSuccessResult() {
Drawable mockDrawable = mock(Drawable.class);
mCreateUserActivity.setSuccessResult(TEST_USER_NAME, mockDrawable, TEST_USER_ICON_PATH,
TEST_IS_ADMIN);
- assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(mCreateUserActivity.isFinishing()).isTrue();
assertThat(shadowOf(mCreateUserActivity).getResultCode()).isEqualTo(Activity.RESULT_OK);
Intent resultIntent = shadowOf(mCreateUserActivity).getResultIntent();
@@ -92,10 +92,10 @@ public class CreateUserActivityTest {
}
@Test
- public void cancel_dismissesDialogAndSetsCancelResult() {
+ public void cancel_finishesActivityAndSetsCancelResult() {
mCreateUserActivity.cancel();
- assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse();
+ assertThat(mCreateUserActivity.isFinishing()).isTrue();
assertThat(shadowOf(mCreateUserActivity).getResultCode())
.isEqualTo(Activity.RESULT_CANCELED);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java
index a47b4d5c354f..093833ec41cf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java
@@ -18,13 +18,22 @@ package com.android.settingslib.widget;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.view.View;
import android.widget.TextView;
+import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.preference.PreferenceScreenFactory;
import com.android.settingslib.widget.mainswitch.R;
import org.junit.Before;
@@ -67,4 +76,31 @@ public class MainSwitchPreferenceTest {
assertThat(mRootView.<MainSwitchBar>requireViewById(
R.id.settingslib_main_switch_bar).isChecked()).isTrue();
}
+
+ @Test
+ public void setOnPreferenceChangeListener() {
+ // Attach preference to preference screen, otherwise `Preference.performClick` does not
+ // interact with underlying datastore
+ new PreferenceScreenFactory(mContext).getOrCreatePreferenceScreen().addPreference(
+ mPreference);
+
+ PreferenceDataStore preferenceDataStore = mock(PreferenceDataStore.class);
+ // always return the provided default value
+ when(preferenceDataStore.getBoolean(any(), anyBoolean())).thenAnswer(
+ invocation -> invocation.getArguments()[1]);
+ mPreference.setPreferenceDataStore(preferenceDataStore);
+
+ String key = "key";
+ mPreference.setKey(key);
+ mPreference.setOnPreferenceChangeListener((preference, newValue) -> false);
+ mPreference.onBindViewHolder(mHolder);
+
+ mPreference.performClick();
+ verify(preferenceDataStore, never()).putBoolean(any(), anyBoolean());
+
+ mPreference.setOnPreferenceChangeListener((preference, newValue) -> true);
+
+ mPreference.performClick();
+ verify(preferenceDataStore).putBoolean(any(), anyBoolean());
+ }
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index dafcc729b8f1..d929b0de391a 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -187,6 +187,9 @@
<!-- Default state of tap to wake -->
<bool name="def_double_tap_to_wake">true</bool>
+ <!-- Default setting for double tap to sleep (Settings.Secure.DOUBLE_TAP_TO_SLEEP) -->
+ <bool name="def_double_tap_to_sleep">false</bool>
+
<!-- Default for Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT -->
<string name="def_nfc_payment_component"></string>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index fc61b1e875f3..d3291b4bac17 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -118,6 +118,8 @@ public class GlobalSettings {
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
Settings.Global.Wearable.AUTO_BEDTIME_MODE,
+ Settings.Global.Wearable.GESTURE_PRIMARY_ACTION_USER_PREFERENCE,
+ Settings.Global.Wearable.GESTURE_DISMISS_ACTION_USER_PREFERENCE,
Settings.Global.FORCE_ENABLE_PSS_PROFILING,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3c70fc15485a..0121d31b9f35 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -85,6 +85,7 @@ public class SecureSettings {
Settings.Secure.MOUNT_UMS_PROMPT,
Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
Settings.Secure.DOUBLE_TAP_TO_WAKE,
+ Settings.Secure.DOUBLE_TAP_TO_SLEEP,
Settings.Secure.WAKE_GESTURE_ENABLED,
Settings.Secure.LONG_PRESS_TIMEOUT,
Settings.Secure.KEY_REPEAT_ENABLED,
@@ -220,6 +221,8 @@ public class SecureSettings {
Settings.Secure.EMERGENCY_GESTURE_ENABLED,
Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED,
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
+ Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED,
+ Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED,
Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
@@ -229,6 +232,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED,
@@ -266,13 +270,12 @@ public class SecureSettings {
Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED,
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB,
Settings.Secure.STYLUS_BUTTONS_ENABLED,
Settings.Secure.STYLUS_HANDWRITING_ENABLED,
Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
Settings.Secure.CREDENTIAL_SERVICE,
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- Settings.Secure.EVEN_DIMMER_MIN_NITS,
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
@@ -295,5 +298,7 @@ public class SecureSettings {
Settings.Secure.FINGERPRINT_APP_ENABLED,
Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
Settings.Secure.DUAL_SHADE,
+ Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED,
+ Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index cf0447f9fb3a..98f5face5e96 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -124,7 +124,8 @@ public class SystemSettings {
Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
Settings.System.NOTIFICATION_COOLDOWN_ALL,
Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- Settings.System.PREFERRED_REGION
+ Settings.System.PREFERRED_REGION,
+ Settings.System.CV_ENABLED
));
if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
settings.add(Settings.System.PEAK_REFRESH_RATE);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 4c6a1ba7db0a..cd6521ff0dc5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -474,5 +474,7 @@ public class GlobalSettingsValidators {
String.valueOf(
Global.Wearable.STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN)
}));
+ VALIDATORS.put(Global.Wearable.GESTURE_PRIMARY_ACTION_USER_PREFERENCE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.GESTURE_DISMISS_ACTION_USER_PREFERENCE, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index c09e45ed81a6..5eb6af62d2c3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -114,9 +114,6 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.FONT_WEIGHT_ADJUSTMENT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_LEVEL, PERCENTAGE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.EVEN_DIMMER_ACTIVATED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.EVEN_DIMMER_MIN_NITS,
- new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.TTS_DEFAULT_RATE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_PITCH, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_SYNTH, PACKAGE_NAME_VALIDATOR);
@@ -134,6 +131,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.MOUNT_UMS_PROMPT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MOUNT_UMS_NOTIFY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DOUBLE_TAP_TO_SLEEP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.KEY_REPEAT_ENABLED, BOOLEAN_VALIDATOR);
@@ -326,6 +324,10 @@ public class SecureSettingsValidators {
Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL));
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
+ new InclusiveIntegerRangeValidator(
+ Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS,
+ Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE));
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
@@ -356,6 +358,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(
Secure.EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
@@ -432,6 +436,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.GLANCEABLE_HUB_ENABLED, new InclusiveIntegerRangeValidator(0, 1));
+ VALIDATORS.put(Secure.WHEN_TO_START_GLANCEABLE_HUB,
+ new InclusiveIntegerRangeValidator(0, 3));
VALIDATORS.put(Secure.STYLUS_BUTTONS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
@@ -464,5 +470,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DUAL_SHADE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.BROWSER_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SEARCH_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4f649ed49be3..3a584401ed72 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -271,5 +271,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.PREFERRED_REGION, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(System.CV_ENABLED,
+ new InclusiveIntegerRangeValidator(0, 1));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index de7c450d8d39..7c975b750639 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -72,6 +72,7 @@ import java.util.regex.Pattern;
public final class DeviceConfigService extends Binder {
private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
"/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
index b0086c180cbd..78c87b389dfc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
+++ b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
@@ -1,2 +1,2 @@
-per-file WritableNamespacePrefixes.java = mpgroover@google.com,tedbauer@google.com
-per-file WritableNamespaces.java = mpgroover@google.com,tedbauer@google.com
+per-file WritableNamespacePrefixes.java = mpgroover@google.com
+per-file WritableNamespaces.java = mpgroover@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 167674a451b3..584b21adbe77 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1881,6 +1881,10 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
+ SecureSettingsProto.Accessibility
+ .ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED);
@@ -2107,6 +2111,12 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
SecureSettingsProto.ADAPTIVE_CONNECTIVITY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED,
+ SecureSettingsProto.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED,
+ SecureSettingsProto.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED);
final long controlsToken = p.start(SecureSettingsProto.CONTROLS);
dumpSetting(s, p,
@@ -2189,15 +2199,6 @@ class SettingsProtoDumpUtil {
Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
SecureSettingsProto.ENHANCED_VOICE_PRIVACY_ENABLED);
- final long evenDimmerToken = p.start(SecureSettingsProto.EVEN_DIMMER);
- dumpSetting(s, p,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- SecureSettingsProto.EvenDimmer.EVEN_DIMMER_ACTIVATED);
- dumpSetting(s, p,
- Settings.Secure.EVEN_DIMMER_MIN_NITS,
- SecureSettingsProto.EvenDimmer.EVEN_DIMMER_MIN_NITS);
- p.end(evenDimmerToken);
-
dumpSetting(s, p,
Settings.Secure.EM_VALUE,
SecureSettingsProto.Accessibility.EM_VALUE);
@@ -3156,6 +3157,12 @@ class SettingsProtoDumpUtil {
SystemSettingsProto.Volume.MASTER_BALANCE);
p.end(volumeToken);
+ final long systemDisplayToken = p.start(SystemSettingsProto.DISPLAY);
+ dumpSetting(s, p,
+ Settings.System.CV_ENABLED,
+ SystemSettingsProto.Display.CV_ENABLED);
+ p.end(systemDisplayToken);
+
dumpSetting(s, p,
Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index bc281eea39d8..65ede9d804d0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -396,6 +396,8 @@ public class SettingsProvider extends ContentProvider {
private volatile SystemConfigManager mSysConfigManager;
+ private PackageMonitor mPackageMonitor;
+
@GuardedBy("mLock")
private boolean mSyncConfigDisabledUntilReboot;
@@ -403,6 +405,7 @@ public class SettingsProvider extends ContentProvider {
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
+
@Override
public boolean onCreate() {
Settings.setInSystemServer();
@@ -1036,7 +1039,7 @@ public class SettingsProvider extends ContentProvider {
}
}, userFilter);
- PackageMonitor monitor = new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
@@ -1062,7 +1065,7 @@ public class SettingsProvider extends ContentProvider {
};
// package changes
- monitor.register(getContext(), BackgroundThread.getHandler().getLooper(),
+ mPackageMonitor.register(getContext(), BackgroundThread.getHandler().getLooper(),
UserHandle.ALL, true);
}
@@ -4077,7 +4080,7 @@ public class SettingsProvider extends ContentProvider {
@VisibleForTesting
final class UpgradeController {
- private static final int SETTINGS_VERSION = 227;
+ private static final int SETTINGS_VERSION = 228;
private final int mUserId;
@@ -6316,6 +6319,23 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 227;
}
+ // Version 227: Add default value for DOUBLE_TAP_TO_SLEEP.
+ if (currentVersion == 227) {
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting doubleTapToSleep = secureSettings.getSettingLocked(
+ Settings.Secure.DOUBLE_TAP_TO_SLEEP);
+ if (doubleTapToSleep.isNull()) {
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOUBLE_TAP_TO_SLEEP,
+ getContext().getResources().getBoolean(
+ R.bool.def_double_tap_to_sleep) ? "1" : "0",
+ null /* tag */,
+ true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 228;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 99c4e21c6053..17c13b78778c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -158,6 +158,7 @@ public class SettingsState {
private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
"/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
diff --git a/packages/Shell/res/values-et/strings.xml b/packages/Shell/res/values-et/strings.xml
index 48c73345ddcc..c18687fd18ff 100644
--- a/packages/Shell/res/values-et/strings.xml
+++ b/packages/Shell/res/values-et/strings.xml
@@ -21,7 +21,7 @@
<string name="bugreport_in_progress_title" msgid="4311705936714972757">"Luuakse veaaruannet <xliff:g id="ID">#%d</xliff:g>"</string>
<string name="bugreport_finished_title" msgid="4429132808670114081">"Jäädvustati veaaruanne <xliff:g id="ID">#%d</xliff:g>"</string>
<string name="bugreport_updating_title" msgid="4423539949559634214">"Üksikasjade lisamine veaaruandesse"</string>
- <string name="bugreport_updating_wait" msgid="3322151947853929470">"Oodake …"</string>
+ <string name="bugreport_updating_wait" msgid="3322151947853929470">"Palun oodake…"</string>
<string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Veaaruanne kuvatakse telefonis peagi"</string>
<string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Veaaruande jagamiseks valige"</string>
<string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Veaaruande jagamiseks puudutage"</string>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index fb0678fedb56..5bba99f84d43 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -255,11 +255,11 @@ public class BugreportProgressService extends Service {
/** Always keep remote bugreport files created in the last day. */
private static final long REMOTE_MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
- /** Minimum delay for sending last update notification */
- private static final int DELAY_NOTIFICATION_MS = 250;
-
private final Object mLock = new Object();
+/** Minimum delay between percentage points before sending an update notification */
+ private static final int MIN_NOTIFICATION_GAP = 10;
+
/** Managed bugreport info (keyed by id) */
@GuardedBy("mLock")
private final SparseArray<BugreportInfo> mBugreportInfos = new SparseArray<>();
@@ -1460,17 +1460,6 @@ public class BugreportProgressService extends Service {
* Sends a notification indicating the bugreport has finished so use can share it.
*/
private void sendBugreportNotification(BugreportInfo info, boolean takingScreenshot) {
-
- final long lastUpdate = System.currentTimeMillis() - info.lastUpdate.longValue();
- if (lastUpdate < DELAY_NOTIFICATION_MS) {
- Log.d(TAG, "Delaying final notification for "
- + (DELAY_NOTIFICATION_MS - lastUpdate) + " ms ");
- mMainThreadHandler.postDelayed(() -> {
- sendBugreportNotification(info, takingScreenshot);
- }, DELAY_NOTIFICATION_MS - lastUpdate);
- return;
- }
-
// Since adding the details can take a while, do it before notifying user.
addDetailsToZipFile(info);
@@ -1523,7 +1512,7 @@ public class BugreportProgressService extends Service {
builder.setSubText(info.getName());
}
- Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
+ Log.d(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
NotificationManager.from(mContext).notify(info.id, builder.build());
}
@@ -2753,6 +2742,11 @@ public class BugreportProgressService extends Service {
if (progress > CAPPED_PROGRESS) {
progress = CAPPED_PROGRESS;
}
+
+ if ((progress - info.lastProgress.intValue()) < MIN_NOTIFICATION_GAP) {
+ return;
+ }
+
if (DEBUG) {
if (progress != info.progress.intValue()) {
Log.v(TAG, "Updating progress for name " + info.getName() + "(id: " + info.id
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java
index 433eca23080e..8031b103d72c 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java
@@ -20,7 +20,7 @@ import static com.android.shell.BugreportProgressService.findSendToAccount;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.accounts.Account;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5b48566d92f9..129949fd38b2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -534,6 +534,7 @@ android_library {
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
"kairos",
+ "displaylib",
"aconfig_settings_flags_lib",
],
libs: [
@@ -728,6 +729,7 @@ android_library {
"Traceur-res",
"aconfig_settings_flags_lib",
"kairos",
+ "displaylib",
],
}
@@ -770,6 +772,7 @@ android_library {
"androidx.compose.runtime_runtime",
"kairos",
"kosmos",
+ "displaylib",
"testables",
"androidx.test.rules",
"platform-compat-test-rules",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 72ae76a45cac..f628a420d8fa 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -312,6 +312,9 @@
<!-- Permission necessary to change car audio volume through CarAudioManager -->
<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+ <!-- To detect when projecting to Android Auto -->
+ <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
+
<!-- Permission to control Android Debug Bridge (ADB) -->
<uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
diff --git a/packages/SystemUI/BUILD_OWNERS b/packages/SystemUI/BUILD_OWNERS
new file mode 100644
index 000000000000..4aadee173388
--- /dev/null
+++ b/packages/SystemUI/BUILD_OWNERS
@@ -0,0 +1,22 @@
+# Build file owners for System UI. Owners should consider the following:
+#
+# - Does the change negatively affect developer builds? Will it make
+# the build slower?
+#
+# - Does the change add unnecessary dependencies or compilation steps
+# that will be difficult to refactor?
+#
+# For more information, see http://go/sysui-build-owners
+
+dsandler@android.com
+
+caitlinshk@google.com
+ccross@android.com
+cinek@google.com
+jernej@google.com
+mankoff@google.com
+nicomazz@google.com
+peskal@google.com
+pixel@google.com
+saff@google.com
+vadimt@google.com
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index f5c0233d56b1..ab3fa1b06255 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -125,3 +125,6 @@ silvajordan@google.com
uwaisashraf@google.com
vinayjoglekar@google.com
willosborn@google.com
+
+per-file *.mk,{**/,}Android.bp = set noparent
+per-file *.mk,{**/,}Android.bp = file:BUILD_OWNERS
diff --git a/packages/SystemUI/TEST_OWNERS b/packages/SystemUI/TEST_OWNERS
index eadc86e386cb..21faf036c8f6 100644
--- a/packages/SystemUI/TEST_OWNERS
+++ b/packages/SystemUI/TEST_OWNERS
@@ -3,3 +3,6 @@
# for restructuring and test maintenance only
saff@google.com
+
+# Work around per-file set noparent includes
+per-file *=saff@google.com
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 71726199aeb6..1543dbe7bb29 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -46,7 +46,7 @@ import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
import android.os.UserManager;
-import android.platform.uiautomator_helpers.WaitUtils;
+import android.platform.uiautomatorhelpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 3cb30258fcb1..c6bc1c70ad18 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -155,3 +155,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "hearing_devices_input_routing_ui_improvement"
+ namespace: "accessibility"
+ description: "UI improvement for hearing device input routing feature"
+ bug: "397314200"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index ee918c275b7b..dd9a8b19f498 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -7,3 +7,13 @@ flag {
description: "Enable Shade Animations"
bug: "327732946"
}
+
+flag {
+ name: "predictive_back_delay_wm_transition"
+ namespace: "systemui"
+ description: "Slightly delays the back transition start"
+ bug: "301195601"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7e8d5496a390..4693377654f8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -213,18 +213,6 @@ flag {
}
flag {
- name: "notification_undo_guts_on_config_changed"
- namespace: "systemui"
- description: "Fixes a bug where a theme or font change while notification guts were open"
- " (e.g. the snooze options or notification info) would show an empty notification by"
- " closing the guts and undoing changes."
- bug: "379267630"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -292,6 +280,17 @@ flag {
}
flag {
+ name: "notification_row_accessibility_expanded"
+ namespace: "systemui"
+ description: "Prepare ExpandableNotificationRow for new A11y expansion APIs."
+ bug: "380027122"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
+flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
@@ -1390,6 +1389,16 @@ flag {
}
flag {
+ name: "media_controls_device_manager_background_execution"
+ namespace: "systemui"
+ description: "Sends some instances creation to background thread"
+ bug: "400200474"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "output_switcher_redesign"
namespace: "systemui"
description: "Enables visual update for Media Output Switcher"
@@ -1434,16 +1443,6 @@ flag {
}
flag {
- name: "notification_media_manager_background_execution"
- namespace: "systemui"
- description: "Decide whether to execute binder calls in background thread"
- bug: "336612071"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "dozeui_scheduling_alarms_background_execution"
namespace: "systemui"
description: "Decide whether to execute binder calls to schedule alarms in background thread"
@@ -1835,6 +1834,16 @@ flag {
}
flag {
+ name: "disable_blurred_shade_visible"
+ namespace: "systemui"
+ description: "Removes the check for a blur radius when determining shade window visibility"
+ bug: "394977231"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_shade_blur"
namespace: "systemui"
description: "Enables the new blur effect on the Notification Shade."
@@ -2103,3 +2112,27 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "always_compose_qs_ui_fragment"
+ namespace: "systemui"
+ description: "Have QQS and QS scenes in the Compose fragment always composed, not just when it should be visible."
+ bug: "389985793"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "move_transition_animation_layer"
+ namespace: "systemui"
+ description: "Enables moving the launching window on top of the origin window in the Animation library."
+ bug: "390422470"
+}
+
+flag {
+ name: "status_bar_chips_return_animations"
+ namespace: "systemui"
+ description: "Enables return animations for status bar chips"
+ bug: "202516970"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index f03bd3d9a2a7..7ee6a6e5ebf4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -62,6 +62,7 @@ import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import com.android.systemui.Flags.moveTransitionAnimationLayer
import com.android.systemui.Flags.translucentOccludingActivityFix
import com.android.systemui.animation.TransitionAnimator.Companion.assertLongLivedReturnAnimations
import com.android.systemui.animation.TransitionAnimator.Companion.assertReturnAnimations
@@ -106,6 +107,16 @@ constructor(
*/
// TODO(b/301385865): Remove this flag.
private val disableWmTimeout: Boolean = false,
+
+ /**
+ * Whether we should disable the reparent transaction that puts the opening/closing window above
+ * the view's window. This should be set to true in tests only, where we can't currently use a
+ * valid leash.
+ *
+ * TODO(b/397180418): Remove this flag when we don't have the RemoteAnimation wrapper anymore
+ * and we can just inject a fake transaction.
+ */
+ private val skipReparentTransaction: Boolean = false,
) {
@JvmOverloads
constructor(
@@ -1139,6 +1150,7 @@ constructor(
DelegatingAnimationCompletionListener(listener, this::dispose),
transitionAnimator,
disableWmTimeout,
+ skipReparentTransaction,
)
}
@@ -1172,6 +1184,16 @@ constructor(
*/
// TODO(b/301385865): Remove this flag.
disableWmTimeout: Boolean = false,
+
+ /**
+ * Whether we should disable the reparent transaction that puts the opening/closing window
+ * above the view's window. This should be set to true in tests only, where we can't
+ * currently use a valid leash.
+ *
+ * TODO(b/397180418): Remove this flag when we don't have the RemoteAnimation wrapper
+ * anymore and we can just inject a fake transaction.
+ */
+ private val skipReparentTransaction: Boolean = false,
) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val transitionContainer = controller.transitionContainer
private val context = transitionContainer.context
@@ -1514,6 +1536,20 @@ constructor(
)
}
+ if (moveTransitionAnimationLayer() && !skipReparentTransaction) {
+ // Ensure that the launching window is rendered above the view's window,
+ // so it is not obstructed.
+ // TODO(b/397180418): re-use the start transaction once the
+ // RemoteAnimation wrapper is cleaned up.
+ SurfaceControl.Transaction().use {
+ it.reparent(
+ window.leash,
+ controller.transitionContainer.viewRootImpl.surfaceControl,
+ )
+ it.apply()
+ }
+ }
+
if (startTransaction != null) {
// Calling applyStateToWindow() here avoids skipping a frame when taking
// over an animation.
@@ -1566,12 +1602,18 @@ constructor(
} else {
null
}
+ val fadeWindowBackgroundLayer =
+ if (moveTransitionAnimationLayer()) {
+ false
+ } else {
+ !controller.isBelowAnimatingWindow
+ }
animation =
transitionAnimator.startAnimation(
controller,
endState,
windowBackgroundColor,
- fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
+ fadeWindowBackgroundLayer = fadeWindowBackgroundLayer,
drawHole = !controller.isBelowAnimatingWindow,
startVelocity = velocityPxPerS,
startFrameTime = windowState?.timestamp ?: -1,
@@ -1685,7 +1727,7 @@ constructor(
// fade in progressively. Otherwise, it should be fully opaque and will be progressively
// revealed as the window background color layer above the window fades out.
val alpha =
- if (controller.isBelowAnimatingWindow) {
+ if (moveTransitionAnimationLayer() || controller.isBelowAnimatingWindow) {
if (controller.isLaunching) {
interpolators.contentAfterFadeInInterpolator.getInterpolation(
windowProgress
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
index 9a746870c6ff..e07d7b337ba2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
@@ -16,17 +16,18 @@
package com.android.systemui.animation
+import kotlin.text.buildString
+
class FontVariationUtils {
private var mWeight = -1
private var mWidth = -1
private var mOpticalSize = -1
private var mRoundness = -1
- private var isUpdated = false
+ private var mCurrentFVar = ""
/*
* generate fontVariationSettings string, used for key in typefaceCache in TextAnimator
* the order of axes should align to the order of parameters
- * if every axis remains unchanged, return ""
*/
fun updateFontVariation(
weight: Int = -1,
@@ -34,15 +35,17 @@ class FontVariationUtils {
opticalSize: Int = -1,
roundness: Int = -1,
): String {
- isUpdated = false
+ var isUpdated = false
if (weight >= 0 && mWeight != weight) {
isUpdated = true
mWeight = weight
}
+
if (width >= 0 && mWidth != width) {
isUpdated = true
mWidth = width
}
+
if (opticalSize >= 0 && mOpticalSize != opticalSize) {
isUpdated = true
mOpticalSize = opticalSize
@@ -52,23 +55,32 @@ class FontVariationUtils {
isUpdated = true
mRoundness = roundness
}
- var resultString = ""
- if (mWeight >= 0) {
- resultString += "'${GSFAxes.WEIGHT.tag}' $mWeight"
- }
- if (mWidth >= 0) {
- resultString +=
- (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH.tag}' $mWidth"
- }
- if (mOpticalSize >= 0) {
- resultString +=
- (if (resultString.isBlank()) "" else ", ") +
- "'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize"
- }
- if (mRoundness >= 0) {
- resultString +=
- (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND.tag}' $mRoundness"
+
+ if (!isUpdated) {
+ return mCurrentFVar
}
- return if (isUpdated) resultString else ""
+
+ return buildString {
+ if (mWeight >= 0) {
+ if (!isBlank()) append(", ")
+ append("'${GSFAxes.WEIGHT.tag}' $mWeight")
+ }
+
+ if (mWidth >= 0) {
+ if (!isBlank()) append(", ")
+ append("'${GSFAxes.WIDTH.tag}' $mWidth")
+ }
+
+ if (mOpticalSize >= 0) {
+ if (!isBlank()) append(", ")
+ append("'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize")
+ }
+
+ if (mRoundness >= 0) {
+ if (!isBlank()) append(", ")
+ append("'${GSFAxes.ROUND.tag}' $mRoundness")
+ }
+ }
+ .also { mCurrentFVar = it }
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
index f4e03613169a..e734dd26eb15 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
@@ -25,6 +25,7 @@ data class AxisDefinition(
)
object GSFAxes {
+ @JvmStatic
val WEIGHT =
AxisDefinition(
tag = "wght",
@@ -91,8 +92,8 @@ object GSFAxes {
private val AXIS_MAP =
listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC)
- .map { def -> def.tag.toLowerCase() to def }
+ .map { def -> def.tag.lowercase() to def }
.toMap()
- fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.toLowerCase()]
+ fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.lowercase()]
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index 694fc8eb6ad7..ca94482b9c5a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -261,7 +262,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
SurfaceControl.Transaction startTransaction
) {
checkArgument(isOpeningMode(launcherChange.getMode()));
- if (!isClosingType(info.getType())) {
+ if (!isClosingType(info.getType())
+ && !ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue()) {
return;
}
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
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 5b073e49192a..4f01d7fcdb51 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -39,6 +39,7 @@ interface TypefaceVariantCache {
fun getTypefaceForVariant(fvar: String?): Typeface?
companion object {
+ @JvmStatic
fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface {
if (fVar.isNullOrEmpty()) {
return baseTypeface
@@ -81,6 +82,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio
}
}
+interface TextAnimatorListener : TextInterpolatorListener {
+ fun onInvalidate() {}
+}
+
/**
* This class provides text animation between two styles.
*
@@ -109,13 +114,19 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio
class TextAnimator(
layout: Layout,
private val typefaceCache: TypefaceVariantCache,
- private val invalidateCallback: () -> Unit = {},
+ private val listener: TextAnimatorListener? = null,
) {
- @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache)
+ var textInterpolator = TextInterpolator(layout, typefaceCache, listener)
@VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) }
var animator: ValueAnimator? = null
+ val progress: Float
+ get() = textInterpolator.progress
+
+ val linearProgress: Float
+ get() = textInterpolator.linearProgress
+
val fontVariationUtils = FontVariationUtils()
sealed class PositionedGlyph {
@@ -287,8 +298,9 @@ class TextAnimator(
animator = buildAnimator(animation).apply { start() }
} else {
textInterpolator.progress = 1f
+ textInterpolator.linearProgress = 1f
textInterpolator.rebase()
- invalidateCallback()
+ listener?.onInvalidate()
}
}
@@ -301,7 +313,7 @@ class TextAnimator(
addUpdateListener {
textInterpolator.progress = it.animatedValue as Float
textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat()
- invalidateCallback()
+ listener?.onInvalidate()
}
addListener(
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 457f45388062..22c5258edb58 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -27,8 +27,19 @@ import android.util.MathUtils
import com.android.internal.graphics.ColorUtils
import java.lang.Math.max
+interface TextInterpolatorListener {
+ fun onPaintModified() {}
+
+ fun onRebased() {}
+}
+
/** Provide text style linear interpolation for plain text. */
-class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) {
+class TextInterpolator(
+ layout: Layout,
+ var typefaceCache: TypefaceVariantCache,
+ private val listener: TextInterpolatorListener? = null,
+) {
+
/**
* Returns base paint used for interpolation.
*
@@ -136,6 +147,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache)
*/
fun onTargetPaintModified() {
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
+ listener?.onPaintModified()
}
/**
@@ -146,6 +158,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache)
*/
fun onBasePaintModified() {
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
+ listener?.onPaintModified()
}
/**
@@ -204,6 +217,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache)
*/
fun rebase() {
if (progress == 0f) {
+ listener?.onRebased()
return
} else if (progress == 1f) {
basePaint.set(targetPaint)
@@ -233,6 +247,8 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache)
}
progress = 0f
+ linearProgress = 0f
+ listener?.onRebased()
}
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 4e889e946a5f..a4a96d19e8bb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -39,6 +39,7 @@ import com.android.app.animation.Interpolators.LINEAR
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
+import com.android.systemui.Flags.moveTransitionAnimationLayer
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import java.util.concurrent.Executor
@@ -509,6 +510,8 @@ class TransitionAnimator(
* punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole]
* is true.
*
+ * TODO(b/397646693): remove drawHole altogether.
+ *
* If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation
* using it for the initial momentum will be used instead of the default interpolators. In this
* case, [startFrameTime] (if non-negative) represents the frame time at which the springs
@@ -1003,13 +1006,32 @@ class TransitionAnimator(
Log.d(TAG, "Animation ended")
}
- // TODO(b/330672236): Post this to the main thread instead so that it does not
- // flicker with Flexiglass enabled.
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
+ val onEnd = {
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
+ }
+ // TODO(b/330672236): Post this to the main thread for launches as well, so that they do not
+ // flicker with Flexiglass enabled.
+ if (controller.isLaunching) {
+ onEnd()
+ } else {
+ // onAnimationEnd is called at the end of the animation, on a Choreographer animation
+ // tick. During dialog launches, the following calls will move the animated content from
+ // the dialog overlay back to its original position, and this change must be reflected
+ // in the next frame given that we then sync the next frame of both the content and
+ // dialog ViewRoots. During SysUI activity launches, we will instantly collapse the
+ // shade at the end of the transition. However, if those are rendered by Compose, whose
+ // compositions are also scheduled on a Choreographer frame, any state change made
+ // *right now* won't be reflected in the next frame given that a Choreographer frame
+ // can't schedule another and have it happen in the same frame. So we post the forwarded
+ // calls to [Controller.onLaunchAnimationEnd] in the main executor, leaving this
+ // Choreographer frame, ensuring that any state change applied by
+ // onTransitionAnimationEnd() will be reflected in the same frame.
+ mainExecutor.execute { onEnd() }
}
}
@@ -1183,6 +1205,10 @@ class TransitionAnimator(
if (drawHole) {
drawable.setXfermode(SRC_MODE)
}
+ } else if (moveTransitionAnimationLayer() && fadeOutProgress >= 1 && drawHole) {
+ // If [drawHole] is true, draw it once the opening content is done fading in.
+ drawable.alpha = 0x00
+ drawable.setXfermode(SRC_MODE)
} else {
drawable.alpha = 0xFF
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt
new file mode 100644
index 000000000000..40fac0d05b96
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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
+import org.jetbrains.uast.getContainingUClass
+
+/**
+ * Detects direct construction of `Kosmos()` in subclasses of SysuiTestCase, which can and should
+ * use `testKosmos`. See go/thetiger
+ */
+class DoNotDirectlyConstructKosmosDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableConstructorTypes() = listOf("com.android.systemui.kosmos.Kosmos")
+
+ override fun visitConstructor(
+ context: JavaContext,
+ node: UCallExpression,
+ constructor: PsiMethod,
+ ) {
+ val superClassNames =
+ node.getContainingUClass()?.superTypes.orEmpty().map { it.resolve()?.qualifiedName }
+ if (superClassNames.contains("com.android.systemui.SysuiTestCase")) {
+ context.report(
+ issue = ISSUE,
+ scope = node,
+ location = context.getLocation(node.methodIdentifier),
+ message = "Prefer testKosmos to direct Kosmos() in sysui tests. go/testkosmos",
+ )
+ }
+ super.visitConstructor(context, node, constructor)
+ }
+
+ companion object {
+ @JvmStatic
+ val ISSUE =
+ Issue.create(
+ id = "DoNotDirectlyConstructKosmos",
+ briefDescription =
+ "Prefer testKosmos to direct Kosmos() in sysui tests. go/testkosmos",
+ explanation =
+ """
+ SysuiTestCase.testKosmos allows us to pre-populate a Kosmos instance with
+ team-standard fixture values, and makes it easier to make centralized changes
+ when necessary. See go/testkosmos
+ """,
+ category = Category.TESTING,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ DoNotDirectlyConstructKosmosDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt
new file mode 100644
index 000000000000..fce536a60b84
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.client.api.UElementHandler
+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 org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+/** Detects whether [runBlocking] is being imported. */
+class RunBlockingDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes(): List<Class<out UElement>> {
+ return listOf(UFile::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ for (importStatement in node.imports) {
+ visitImportStatement(context, importStatement)
+ }
+ }
+ }
+ }
+
+ private fun visitImportStatement(context: JavaContext, importStatement: UImportStatement) {
+ val importName = importStatement.importReference?.asSourceString()
+ if (FORBIDDEN_IMPORTS.contains(importName)) {
+ context.report(
+ ISSUE,
+ importStatement as UElement,
+ context.getLocation(importStatement),
+ "Importing $importName is not allowed.",
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE =
+ Issue.create(
+ id = "RunBlockingUsage",
+ briefDescription = "Discouraged runBlocking call",
+ explanation =
+ """
+ Using `runBlocking` is generally discouraged in Android
+ development as it can lead to UI freezes and ANRs.
+ Consider using `launch` or `async` with coroutine scope
+ instead. If needed from java, consider introducing a method
+ with a callback instead from kotlin.
+ """,
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(RunBlockingDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+
+ val FORBIDDEN_IMPORTS = listOf("kotlinx.coroutines.runBlocking")
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt
index 4927fb9dc67d..13ffa6c5deaa 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt
@@ -29,16 +29,12 @@ import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.getContainingUFile
-/**
- * Detects test function naming violations regarding use of the backtick-wrapped space-allowed
- * feature of Kotlin functions.
- */
+/** Detects use of `TestScope.runTest` when we should use `Kosmos.runTest` by go/kosmos-runtest */
class RunTestShouldUseKosmosDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames() = listOf("runTest")
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (method.getReceiver()?.qualifiedName == "kotlinx.coroutines.test.TestScope") {
-
val imports =
node.getContainingUFile()?.imports.orEmpty().mapNotNull {
it.importReference?.asSourceString()
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 adb311610587..b455c0021517 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
@@ -50,6 +50,7 @@ class SystemUIIssueRegistry : IssueRegistry() {
ShadeDisplayAwareDialogDetector.ISSUE,
RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING,
RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR,
+ RunBlockingDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt
new file mode 100644
index 000000000000..20f6bcbdbbfe
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintResult
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class DoNotDirectlyConstructKosmosTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector = DoNotDirectlyConstructKosmosDetector()
+
+ override fun getIssues(): List<Issue> = listOf(DoNotDirectlyConstructKosmosDetector.ISSUE)
+
+ @Test
+ fun wronglyTriesToDirectlyConstructKosmos() {
+ val runOnSource =
+ runOnSource(
+ """
+ package test.pkg.name
+
+ import com.android.systemui.kosmos.Kosmos
+ import com.android.systemui.SysuiTestCase
+
+ class MyTest: SysuiTestCase {
+ val kosmos = Kosmos()
+ }
+ """
+ )
+
+ runOnSource
+ .expectWarningCount(1)
+ .expect(
+ """
+ src/test/pkg/name/MyTest.kt:7: Warning: Prefer testKosmos to direct Kosmos() in sysui tests. go/testkosmos [DoNotDirectlyConstructKosmos]
+ val kosmos = Kosmos()
+ ~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun okToConstructKosmosIfNotInSysuiTestCase() {
+ val runOnSource =
+ runOnSource(
+ """
+ package test.pkg.name
+
+ import com.android.systemui.kosmos.Kosmos
+
+ class MyTest {
+ val kosmos = Kosmos()
+ }
+ """
+ )
+
+ runOnSource.expectWarningCount(0)
+ }
+
+ private fun runOnSource(source: String): TestLintResult {
+ return lint()
+ .files(TestFiles.kotlin(source).indented(), kosmosStub, sysuiTestCaseStub)
+ .issues(DoNotDirectlyConstructKosmosDetector.ISSUE)
+ .run()
+ }
+
+ companion object {
+ private val kosmosStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.kosmos
+
+ class Kosmos
+ """
+ )
+
+ private val sysuiTestCaseStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui
+
+ class SysuiTestCase
+ """
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt
new file mode 100644
index 000000000000..4ae429d204aa
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class RunBlockingDetectorTest : SystemUILintDetectorTest() {
+
+ override fun getDetector(): Detector = RunBlockingDetector()
+
+ override fun getIssues(): List<Issue> = listOf(RunBlockingDetector.ISSUE)
+
+ @Test
+ fun testViolation() {
+ lint()
+ .files(
+ kotlin(
+ """
+ package com.example
+
+ import kotlinx.coroutines.runBlocking
+
+ class MyClass {
+ fun myMethod() {
+ runBlocking {
+ // Some code here
+ }
+ }
+ }
+ """
+ ),
+ RUN_BLOCKING_DEFINITION,
+ )
+ .issues(RunBlockingDetector.ISSUE)
+ .run()
+ .expect(
+ """
+src/com/example/MyClass.kt:4: Warning: Importing kotlinx.coroutines.runBlocking is not allowed. [RunBlockingUsage]
+ import kotlinx.coroutines.runBlocking
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+"""
+ .trimIndent()
+ )
+ }
+
+ // Verifies that the lint check does *not* flag calls to other methods.
+ @Test
+ fun testNotViolation() {
+ lint()
+ .detector(RunBlockingDetector())
+ .issues(RunBlockingDetector.ISSUE)
+ .files(
+ kotlin(
+ """
+ package com.example
+
+ class MyClass {
+ fun myMethod() {
+ myOtherMethod {
+ }
+ }
+
+ fun myOtherMethod(block: () -> Unit) {
+ block()
+ }
+ }
+ """
+ )
+ )
+ .run()
+ .expectClean()
+ }
+
+ private companion object {
+ val RUN_BLOCKING_DEFINITION =
+ kotlin(
+ """
+ package kotlinx.coroutines
+
+ fun runBlocking(block: suspend () -> Unit) {
+ // Implementation details don't matter for this test.
+ }
+ """
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
index 3f2f84b95977..827096996f0b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
@@ -17,13 +17,20 @@
package com.android.compose.animation
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import kotlin.math.roundToInt
/** A component that can bounce in one dimension, for instance when it is tapped. */
+@Stable
interface Bounceable {
val bounce: Dp
}
@@ -46,6 +53,7 @@ interface Bounceable {
* RTL layouts) side. This can be used for grids for which the last item does not align perfectly
* with the end of the grid.
*/
+@Stable
fun Modifier.bounceable(
bounceable: Bounceable,
previousBounceable: Bounceable?,
@@ -53,7 +61,47 @@ fun Modifier.bounceable(
orientation: Orientation,
bounceEnd: Boolean = nextBounceable != null,
): Modifier {
- return layout { measurable, constraints ->
+ return this then
+ BounceableElement(bounceable, previousBounceable, nextBounceable, orientation, bounceEnd)
+}
+
+private data class BounceableElement(
+ private val bounceable: Bounceable,
+ private val previousBounceable: Bounceable?,
+ private val nextBounceable: Bounceable?,
+ private val orientation: Orientation,
+ private val bounceEnd: Boolean,
+) : ModifierNodeElement<BounceableNode>() {
+ override fun create(): BounceableNode {
+ return BounceableNode(
+ bounceable,
+ previousBounceable,
+ nextBounceable,
+ orientation,
+ bounceEnd,
+ )
+ }
+
+ override fun update(node: BounceableNode) {
+ node.bounceable = bounceable
+ node.previousBounceable = previousBounceable
+ node.nextBounceable = nextBounceable
+ node.orientation = orientation
+ node.bounceEnd = bounceEnd
+ }
+}
+
+private class BounceableNode(
+ var bounceable: Bounceable,
+ var previousBounceable: Bounceable?,
+ var nextBounceable: Bounceable?,
+ var orientation: Orientation,
+ var bounceEnd: Boolean = nextBounceable != null,
+) : Modifier.Node(), LayoutModifierNode {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
// The constraints in the orientation should be fixed, otherwise there is no way to know
// what the size of our child node will be without this animation code.
checkFixedSize(constraints, orientation)
@@ -61,10 +109,12 @@ fun Modifier.bounceable(
var sizePrevious = 0f
var sizeNext = 0f
+ val previousBounceable = previousBounceable
if (previousBounceable != null) {
sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx()
}
+ val nextBounceable = nextBounceable
if (nextBounceable != null) {
sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx()
} else if (bounceEnd) {
@@ -84,7 +134,7 @@ fun Modifier.bounceable(
// constraints, otherwise the parent will automatically center this node given the
// size that it expects us to be. This allows us to then place the element where we
// want it to be.
- layout(idleWidth, placeable.height) {
+ return layout(idleWidth, placeable.height) {
placeable.placeRelative(-sizePrevious.roundToInt(), 0)
}
}
@@ -95,7 +145,7 @@ fun Modifier.bounceable(
constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight)
val placeable = measurable.measure(animatedConstraints)
- layout(placeable.width, idleHeight) {
+ return layout(placeable.width, idleHeight) {
placeable.placeRelative(0, -sizePrevious.roundToInt())
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index a352b1eebb81..82e5f5bb6dc8 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -21,7 +21,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -62,6 +61,7 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
@@ -82,6 +82,7 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.android.compose.modifiers.animatedBackground
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay
import com.android.systemui.animation.ComposableControllerFactory
@@ -291,7 +292,7 @@ fun Expandable(
.updateExpandableSize()
.then(minInteractiveSizeModifier)
.then(clickModifier(controller, onClick, interactionSource))
- .background(color, shape)
+ .animatedBackground(color, shape = shape)
.border(controller)
.onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
) {
@@ -307,19 +308,27 @@ private fun WrappedContent(
contentColor: Color,
content: @Composable (Expandable) -> Unit,
) {
- CompositionLocalProvider(LocalContentColor provides contentColor) {
- // We make sure that the content itself (wrapped by the background) is at least 40.dp, which
- // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to
- // write expandables that are sometimes clickable and sometimes not. There shouldn't be any
- // Expandable smaller than 40dp because if the expandable is not clickable directly, then
- // something in its content should be (and with a size >= 40dp).
- val minSize = 40.dp
- Box(
- Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
- contentAlignment = Alignment.Center,
- ) {
- content(expandable)
+ val minSizeContent =
+ @Composable {
+ // We make sure that the content itself (wrapped by the background) is at least 40.dp,
+ // which is the same as the M3 buttons. This applies even if onClick is null, to make it
+ // easier to write expandables that are sometimes clickable and sometimes not. There
+ // shouldn't be any Expandable smaller than 40dp because if the expandable is not
+ // clickable directly, then something in its content should be (and with a size >=
+ // 40dp).
+ val minSize = 40.dp
+ Box(
+ Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ content(expandable)
+ }
}
+
+ if (contentColor.isSpecified) {
+ CompositionLocalProvider(LocalContentColor provides contentColor, content = minSizeContent)
+ } else {
+ minSizeContent()
}
}
@@ -345,7 +354,7 @@ private fun Modifier.expandable(
.thenIf(drawContent) {
Modifier.border(controller)
.then(clickModifier(controller, onClick, interactionSource))
- .background(controller.color, controller.shape)
+ .animatedBackground(controller.color, shape = controller.shape)
}
.onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() }
.drawWithContent {
@@ -422,7 +431,7 @@ private class DrawExpandableInOverlayNode(
// Background.
this@draw.drawBackground(
state,
- controller.color,
+ controller.color(),
controller.borderStroke,
size = Size(state.width.toFloat(), state.height.toFloat()),
)
@@ -469,7 +478,7 @@ private fun clickModifier(
/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
@Composable
private fun AnimatedContentInOverlay(
- color: Color,
+ color: () -> Color,
sizeInOriginalLayout: Size,
overlay: ViewGroupOverlay,
controller: ExpandableControllerImpl,
@@ -523,7 +532,7 @@ private fun AnimatedContentInOverlay(
return@drawWithContent
}
- drawBackground(animatorState, color, controller.borderStroke)
+ drawBackground(animatorState, color(), controller.borderStroke)
drawContent()
},
// We center the content in the expanding container.
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 72da175e26cf..d7d5a48e2b79 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -26,7 +26,6 @@ import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -80,6 +79,24 @@ fun rememberExpandableController(
borderStroke: BorderStroke? = null,
transitionControllerFactory: ComposableControllerFactory? = null,
): ExpandableController {
+ return rememberExpandableController(
+ color = { color },
+ shape = shape,
+ contentColor = contentColor,
+ borderStroke = borderStroke,
+ transitionControllerFactory = transitionControllerFactory,
+ )
+}
+
+/** Create an [ExpandableController] to control an [Expandable]. */
+@Composable
+fun rememberExpandableController(
+ color: () -> Color,
+ shape: Shape,
+ contentColor: Color = Color.Unspecified,
+ borderStroke: BorderStroke? = null,
+ transitionControllerFactory: ComposableControllerFactory? = null,
+): ExpandableController {
val composeViewRoot = LocalView.current
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
@@ -125,7 +142,7 @@ fun rememberExpandableController(
}
internal class ExpandableControllerImpl(
- internal val color: Color,
+ internal val color: () -> Color,
internal val contentColor: Color,
internal val shape: Shape,
internal val borderStroke: BorderStroke?,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 959f28f2d99e..80cbbea7659f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -95,10 +95,20 @@ interface NestedDraggable {
* nested scrollable.
*
* This is called whenever a nested scrollable does not consume some scroll amount. If this
- * returns `true`, then [onDragStarted] will be called and this draggable will have priority and
+ * returns `true`, then [onDragStarted] will be called, this draggable will have priority and
* consume all future events during preScroll until the nested scroll is finished.
*/
- fun shouldConsumeNestedScroll(sign: Float): Boolean
+ fun shouldConsumeNestedPostScroll(sign: Float): Boolean = true
+
+ /**
+ * Whether this draggable should consume any scroll amount with the given [sign] *before* it can
+ * be consumed by a nested scrollable.
+ *
+ * This is called before a nested scrollable is able to consume that scroll amount. If this
+ * returns `true`, then [onDragStarted] will be called, this draggable will have priority and
+ * consume all future scroll events during preScroll until the nested scroll is finished.
+ */
+ fun shouldConsumeNestedPreScroll(sign: Float): Boolean = false
interface Controller {
/**
@@ -540,6 +550,14 @@ private class NestedDraggableNode(
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val sign = available.toFloat().sign
+ maybeCreateNewController(
+ sign = sign,
+ condition = {
+ source == NestedScrollSource.UserInput &&
+ draggable.shouldConsumeNestedPreScroll(sign)
+ },
+ )
val controller = nestedScrollController ?: return Offset.Zero
return scrollWithOverscroll(controller, available)
}
@@ -560,28 +578,34 @@ private class NestedDraggableNode(
}
val sign = offset.sign
+ maybeCreateNewController(
+ sign,
+ condition = { draggable.shouldConsumeNestedPostScroll(sign) },
+ )
+ val controller = nestedScrollController ?: return Offset.Zero
+ return scrollWithOverscroll(controller, available)
+ }
+
+ private fun maybeCreateNewController(sign: Float, condition: () -> Boolean) {
if (
- nestedDragsEnabled &&
- nestedScrollController == null &&
- // TODO(b/388231324): Remove this.
- !lastEventWasScrollWheel &&
- draggable.shouldConsumeNestedScroll(sign) &&
- lastFirstDown != null
+ !nestedDragsEnabled ||
+ nestedScrollController != null ||
+ lastEventWasScrollWheel ||
+ lastFirstDown == null ||
+ !condition()
) {
- val startedPosition = checkNotNull(lastFirstDown)
-
- // TODO(b/382665591): Ensure that there is at least one pointer down.
- val pointersDownCount = pointersDown.size.coerceAtLeast(1)
- val pointerType = pointersDown.entries.firstOrNull()?.value
- nestedScrollController =
- NestedScrollController(
- overscrollEffect,
- draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType),
- )
+ return
}
- val controller = nestedScrollController ?: return Offset.Zero
- return scrollWithOverscroll(controller, available)
+ // TODO(b/382665591): Ensure that there is at least one pointer down.
+ val pointersDownCount = pointersDown.size.coerceAtLeast(1)
+ val pointerType = pointersDown.entries.firstOrNull()?.value
+ val startedPosition = checkNotNull(lastFirstDown)
+ nestedScrollController =
+ NestedScrollController(
+ overscrollEffect,
+ draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType),
+ )
}
private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt
new file mode 100644
index 000000000000..5b1f0a7c6eb6
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.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.compose.modifiers
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
+import androidx.compose.ui.node.invalidateDraw
+import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * Draws a background in a given [shape] and with a [color] or [alpha] that can be animated.
+ *
+ * @param color color to paint background with
+ * @param alpha alpha of the background
+ * @param shape desired shape of the background
+ */
+fun Modifier.animatedBackground(
+ color: () -> Color,
+ alpha: () -> Float = DefaultAlpha,
+ shape: Shape = RectangleShape,
+) =
+ this.then(
+ BackgroundElement(
+ color = color,
+ alpha = alpha,
+ shape = shape,
+ inspectorInfo =
+ debugInspectorInfo {
+ name = "background"
+ value = color
+ properties["color"] = color
+ properties["alpha"] = alpha
+ properties["shape"] = shape
+ },
+ )
+ )
+
+private val DefaultAlpha = { 1f }
+
+private class BackgroundElement(
+ private val color: () -> Color,
+ private val alpha: () -> Float,
+ private val shape: Shape,
+ private val inspectorInfo: InspectorInfo.() -> Unit,
+) : ModifierNodeElement<BackgroundNode>() {
+ override fun create(): BackgroundNode {
+ return BackgroundNode(color, alpha, shape)
+ }
+
+ override fun update(node: BackgroundNode) {
+ node.color = color
+ node.alpha = alpha
+ node.shape = shape
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ inspectorInfo()
+ }
+
+ override fun hashCode(): Int {
+ var result = color.hashCode()
+ result = 31 * result + alpha.hashCode()
+ result = 31 * result + shape.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ val otherModifier = other as? BackgroundElement ?: return false
+ return color == otherModifier.color &&
+ alpha == otherModifier.alpha &&
+ shape == otherModifier.shape
+ }
+}
+
+private class BackgroundNode(var color: () -> Color, var alpha: () -> Float, var shape: Shape) :
+ DrawModifierNode, Modifier.Node(), ObserverModifierNode {
+
+ // Naively cache outline calculation if input parameters are the same, we manually observe
+ // reads inside shape#createOutline separately
+ private var lastSize: Size = Size.Unspecified
+ private var lastLayoutDirection: LayoutDirection? = null
+ private var lastOutline: Outline? = null
+ private var lastShape: Shape? = null
+ private var tmpOutline: Outline? = null
+
+ override fun ContentDrawScope.draw() {
+ if (shape === RectangleShape) {
+ // shortcut to avoid Outline calculation and allocation
+ drawRect()
+ } else {
+ drawOutline()
+ }
+ drawContent()
+ }
+
+ override fun onObservedReadsChanged() {
+ // Reset cached properties
+ lastSize = Size.Unspecified
+ lastLayoutDirection = null
+ lastOutline = null
+ lastShape = null
+ // Invalidate draw so we build the cache again - this is needed because observeReads within
+ // the draw scope obscures the state reads from the draw scope's observer
+ invalidateDraw()
+ }
+
+ private fun ContentDrawScope.drawRect() {
+ drawRect(color = color(), alpha = alpha())
+ }
+
+ private fun ContentDrawScope.drawOutline() {
+ val outline = getOutline()
+ drawOutline(outline, color = color(), alpha = alpha())
+ }
+
+ private fun ContentDrawScope.getOutline(): Outline {
+ val outline: Outline?
+ if (size == lastSize && layoutDirection == lastLayoutDirection && lastShape == shape) {
+ outline = lastOutline!!
+ } else {
+ // Manually observe reads so we can directly invalidate the outline when it changes
+ // Use tmpOutline to avoid creating an object reference to local var outline
+ observeReads { tmpOutline = shape.createOutline(size, layoutDirection, this) }
+ outline = tmpOutline
+ tmpOutline = null
+ }
+ lastOutline = outline
+ lastSize = size
+ lastLayoutDirection = layoutDirection
+ lastShape = shape
+ return outline!!
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt
deleted file mode 100644
index 794b7a4a3d30..000000000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt
+++ /dev/null
@@ -1,113 +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.compose.modifiers
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.DrawModifier
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.drawOutline
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.LayoutDirection
-
-/**
- * Draws a fading [shape] with a solid [color] and [alpha] behind the content.
- *
- * @param color color to paint background with
- * @param alpha alpha of the background
- * @param shape desired shape of the background
- */
-fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = RectangleShape) =
- this.then(
- FadingBackground(
- brush = SolidColor(color),
- alpha = alpha,
- shape = shape,
- inspectorInfo =
- debugInspectorInfo {
- name = "background"
- value = color
- properties["color"] = color
- properties["alpha"] = alpha
- properties["shape"] = shape
- },
- )
- )
-
-private class FadingBackground
-constructor(
- private val brush: Brush,
- private val shape: Shape,
- private val alpha: () -> Float,
- inspectorInfo: InspectorInfo.() -> Unit,
-) : DrawModifier, InspectorValueInfo(inspectorInfo) {
- // naive cache outline calculation if size is the same
- private var lastSize: Size? = null
- private var lastLayoutDirection: LayoutDirection? = null
- private var lastOutline: Outline? = null
-
- override fun ContentDrawScope.draw() {
- if (shape === RectangleShape) {
- // shortcut to avoid Outline calculation and allocation
- drawRect()
- } else {
- drawOutline()
- }
- drawContent()
- }
-
- private fun ContentDrawScope.drawRect() {
- drawRect(brush, alpha = alpha())
- }
-
- private fun ContentDrawScope.drawOutline() {
- val outline =
- if (size == lastSize && layoutDirection == lastLayoutDirection) {
- lastOutline!!
- } else {
- shape.createOutline(size, layoutDirection, this)
- }
- drawOutline(outline, brush = brush, alpha = alpha())
- lastOutline = outline
- lastSize = size
- lastLayoutDirection = layoutDirection
- }
-
- override fun hashCode(): Int {
- var result = brush.hashCode()
- result = 31 * result + alpha.hashCode()
- result = 31 * result + shape.hashCode()
- return result
- }
-
- override fun equals(other: Any?): Boolean {
- val otherModifier = other as? FadingBackground ?: return false
- return brush == otherModifier.brush &&
- alpha == otherModifier.alpha &&
- shape == otherModifier.shape
- }
-
- override fun toString(): String = "FadingBackground(brush=$brush, alpha = $alpha, shape=$shape)"
-}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
index d08d859ec0d7..fc4d53af4b53 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
@@ -22,7 +22,6 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -32,7 +31,6 @@ import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.modifier.ModifierLocalModifierNode
import androidx.compose.ui.node.DrawModifierNode
@@ -50,11 +48,7 @@ import androidx.compose.ui.util.fastForEach
* The elements redirected to this container will be drawn above the content of this composable.
*/
fun Modifier.container(state: ContainerState): Modifier {
- return onPlaced { state.lastOffsetInWindow = it.positionInWindow() }
- .drawWithContent {
- drawContent()
- state.drawInOverlay(this)
- }
+ return this then ContainerElement(state)
}
/**
@@ -105,6 +99,30 @@ internal interface LayerRenderer {
fun drawInOverlay(drawScope: DrawScope)
}
+private data class ContainerElement(private val state: ContainerState) :
+ ModifierNodeElement<ContainerNode>() {
+ override fun create(): ContainerNode {
+ return ContainerNode(state)
+ }
+
+ override fun update(node: ContainerNode) {
+ node.state = state
+ }
+}
+
+/** A node implementing [container] that can be delegated to. */
+class ContainerNode(var state: ContainerState) :
+ Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode {
+ override fun onPlaced(coordinates: LayoutCoordinates) {
+ state.lastOffsetInWindow = coordinates.positionInWindow()
+ }
+
+ override fun ContentDrawScope.draw() {
+ drawContent()
+ state.drawInOverlay(this)
+ }
+}
+
private data class DrawInContainerElement(
var state: ContainerState,
var enabled: () -> Boolean,
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index d8e46ad34c37..a03d769c9c36 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -971,6 +971,35 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
assertThat(availableToEffectPostFling).isWithin(1f).of(100f)
}
+ @Test
+ fun consumeNestedPreScroll() {
+ var consumeNestedPreScroll by mutableStateOf(false)
+ val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll })
+
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ // Always consume everything so that the only way to start the drag is to
+ // intercept preScroll events.
+ .scrollable(rememberScrollableState { it }, orientation)
+ )
+ }
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy((touchSlop + 1f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ consumeNestedPreScroll = true
+ rule.onRoot().performTouchInput { moveBy(1f.toOffset()) }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ }
+
private fun ComposeContentTestRule.setContentWithTouchSlop(
content: @Composable () -> Unit
): Float {
@@ -996,7 +1025,8 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
{ velocity, _ ->
velocity
},
- private val shouldConsumeNestedScroll: (Float) -> Boolean = { true },
+ private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true },
+ private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false },
) : NestedDraggable {
var shouldStartDrag = true
var onDragStartedCalled = false
@@ -1042,8 +1072,12 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
}
}
- override fun shouldConsumeNestedScroll(sign: Float): Boolean {
- return shouldConsumeNestedScroll.invoke(sign)
+ override fun shouldConsumeNestedPostScroll(sign: Float): Boolean {
+ return shouldConsumeNestedPostScroll.invoke(sign)
+ }
+
+ override fun shouldConsumeNestedPreScroll(sign: Float): Boolean {
+ return shouldConsumeNestedPreScroll.invoke(sign)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/PagerDots.kt
index 91f1477d5325..172d88af4cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PagerDots.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/PagerDots.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.compose
+package com.android.systemui.common.ui.compose
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Canvas
@@ -43,9 +43,9 @@ import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import com.android.app.tracing.coroutines.launchTraced as launch
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import platform.test.motion.compose.values.MotionTestValueKey
import platform.test.motion.compose.values.motionTestValues
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 4e1aab58ac24..3150e94908cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -182,7 +182,7 @@ fun CommunalContainer(
viewModel.communalBackground.collectAsStateWithLifecycle(
initialValue = CommunalBackgroundType.ANIMATED
)
- val swipeToHubEnabled by viewModel.swipeToHubEnabled.collectAsStateWithLifecycle()
+ val swipeToHubEnabled by viewModel.swipeToHubEnabled.collectAsStateWithLifecycle(false)
val state: MutableSceneTransitionLayoutState =
rememberMutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 8ad96a5bcb37..62b134279267 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -77,6 +77,16 @@ internal constructor(
list.apply { add(toIndex, removeAt(fromIndex)) }
}
+ /** Swap the two items in the list with the given indices. */
+ fun swapItems(index1: Int, index2: Int) {
+ list.apply {
+ val item1 = get(index1)
+ val item2 = get(index2)
+ set(index2, item1)
+ set(index1, item2)
+ }
+ }
+
/** Remove widget from the list and the database. */
fun onRemove(indexToRemove: Int) {
if (list[indexToRemove].isWidgetContent()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 0aef7f2c7063..dda388aeeac6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -18,8 +18,10 @@ package com.android.systemui.communal.ui.compose
import android.content.ClipDescription
import android.view.DragEvent
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
@@ -37,6 +39,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.android.systemui.Flags.communalWidgetResizing
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.util.WidgetPickerIntentUtils
@@ -51,13 +54,14 @@ import kotlinx.coroutines.launch
* @see dragAndDropTarget
*/
@Composable
-internal fun rememberDragAndDropTargetState(
+fun rememberDragAndDropTargetState(
gridState: LazyGridState,
contentOffset: Offset,
contentListState: ContentListState,
): DragAndDropTargetState {
val scope = rememberCoroutineScope()
val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+
val state =
remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) {
DragAndDropTargetState(
@@ -68,11 +72,9 @@ internal fun rememberDragAndDropTargetState(
scope = scope,
)
}
- LaunchedEffect(state) {
- for (diff in state.scrollChannel) {
- gridState.scrollBy(diff)
- }
- }
+
+ LaunchedEffect(state) { state.processScrollRequests(scope) }
+
return state
}
@@ -83,7 +85,7 @@ internal fun rememberDragAndDropTargetState(
* @see DragEvent
*/
@Composable
-internal fun Modifier.dragAndDropTarget(dragDropTargetState: DragAndDropTargetState): Modifier {
+fun Modifier.dragAndDropTarget(dragDropTargetState: DragAndDropTargetState): Modifier {
val state by rememberUpdatedState(dragDropTargetState)
return this then
@@ -132,13 +134,79 @@ internal fun Modifier.dragAndDropTarget(dragDropTargetState: DragAndDropTargetSt
* other activities. [GridDragDropState] on the other hand, handles dragging of existing items in
* the communal hub grid.
*/
-internal class DragAndDropTargetState(
+class DragAndDropTargetState(
+ state: LazyGridState,
+ contentOffset: Offset,
+ contentListState: ContentListState,
+ autoScrollThreshold: Float,
+ scope: CoroutineScope,
+) {
+ private val dragDropState: DragAndDropTargetStateInternal =
+ if (glanceableHubV2()) {
+ DragAndDropTargetStateV2(
+ state = state,
+ contentListState = contentListState,
+ scope = scope,
+ autoScrollThreshold = autoScrollThreshold,
+ contentOffset = contentOffset,
+ )
+ } else {
+ DragAndDropTargetStateV1(
+ state = state,
+ contentListState = contentListState,
+ scope = scope,
+ autoScrollThreshold = autoScrollThreshold,
+ contentOffset = contentOffset,
+ )
+ }
+
+ fun onStarted() = dragDropState.onStarted()
+
+ fun onMoved(event: DragAndDropEvent) = dragDropState.onMoved(event)
+
+ fun onDrop(event: DragAndDropEvent) = dragDropState.onDrop(event)
+
+ fun onEnded() = dragDropState.onEnded()
+
+ fun onExited() = dragDropState.onExited()
+
+ suspend fun processScrollRequests(coroutineScope: CoroutineScope) =
+ dragDropState.processScrollRequests(coroutineScope)
+}
+
+/**
+ * A private interface defining the API for handling drag-and-drop operations. There will be two
+ * implementations of this interface: V1 for devices that do not have the glanceable_hub_v2 flag
+ * enabled, and V2 for devices that do have that flag enabled.
+ *
+ * TODO(b/400789179): Remove this interface and the V1 implementation once glanceable_hub_v2 has
+ * shipped.
+ */
+private interface DragAndDropTargetStateInternal {
+ fun onStarted() = Unit
+
+ fun onMoved(event: DragAndDropEvent) = Unit
+
+ fun onDrop(event: DragAndDropEvent): Boolean = false
+
+ fun onEnded() = Unit
+
+ fun onExited() = Unit
+
+ suspend fun processScrollRequests(coroutineScope: CoroutineScope) = Unit
+}
+
+/**
+ * The V1 implementation of DragAndDropTargetStateInternal to be used when the glanceable_hub_v2
+ * flag is disabled.
+ */
+private class DragAndDropTargetStateV1(
private val state: LazyGridState,
private val contentOffset: Offset,
private val contentListState: ContentListState,
private val autoScrollThreshold: Float,
private val scope: CoroutineScope,
-) {
+) : DragAndDropTargetStateInternal {
/**
* The placeholder item that is treated as if it is being dragged across the grid. It is added
* to grid once drag and drop event is started and removed when event ends.
@@ -147,15 +215,21 @@ internal class DragAndDropTargetState(
private var placeHolderIndex: Int? = null
private var previousTargetItemKey: Any? = null
- internal val scrollChannel = Channel<Float>()
+ private val scrollChannel = Channel<Float>()
- fun onStarted() {
+ override suspend fun processScrollRequests(coroutineScope: CoroutineScope) {
+ for (diff in scrollChannel) {
+ state.scrollBy(diff)
+ }
+ }
+
+ override fun onStarted() {
// assume item will be added to the end.
contentListState.list.add(placeHolder)
placeHolderIndex = contentListState.list.size - 1
}
- fun onMoved(event: DragAndDropEvent) {
+ override fun onMoved(event: DragAndDropEvent) {
val dragOffset = event.toOffset()
val targetItem =
@@ -201,7 +275,7 @@ internal class DragAndDropTargetState(
}
}
- fun onDrop(event: DragAndDropEvent): Boolean {
+ override fun onDrop(event: DragAndDropEvent): Boolean {
return placeHolderIndex?.let { dropIndex ->
val widgetExtra = event.maybeWidgetExtra() ?: return false
val (componentName, user) = widgetExtra
@@ -219,13 +293,13 @@ internal class DragAndDropTargetState(
} ?: false
}
- fun onEnded() {
+ override fun onEnded() {
placeHolderIndex = null
previousTargetItemKey = null
contentListState.list.remove(placeHolder)
}
- fun onExited() {
+ override fun onExited() {
onEnded()
}
@@ -257,16 +331,186 @@ internal class DragAndDropTargetState(
contentListState.onMove(currentIndex, index)
}
}
+}
+/**
+ * The V2 implementation of DragAndDropTargetStateInternal to be used when the glanceable_hub_v2
+ * flag is enabled.
+ */
+private class DragAndDropTargetStateV2(
+ private val state: LazyGridState,
+ private val contentOffset: Offset,
+ private val contentListState: ContentListState,
+ private val autoScrollThreshold: Float,
+ private val scope: CoroutineScope,
+) : DragAndDropTargetStateInternal {
/**
- * Parses and returns the intent extra associated with the widget that is dropped into the grid.
- *
- * Returns null if the drop event didn't include intent information.
+ * The placeholder item that is treated as if it is being dragged across the grid. It is added
+ * to grid once drag and drop event is started and removed when event ends.
*/
- private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? {
- val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
- return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) }
+ private var placeHolder = CommunalContentModel.WidgetPlaceholder()
+ private var placeHolderIndex: Int? = null
+ private var previousTargetItemKey: Any? = null
+ private var dragOffset = Offset.Zero
+ private var columnWidth = 0
+
+ private val scrollChannel = Channel<Float>()
+
+ override suspend fun processScrollRequests(coroutineScope: CoroutineScope) {
+ while (true) {
+ val amount = scrollChannel.receive()
+
+ if (state.isScrollInProgress) {
+ // Ignore overscrolling if a scroll is already in progress (but we still want to
+ // consume the scroll event so that we don't end up processing a bunch of old
+ // events after scrolling has finished).
+ continue
+ }
+
+ // Perform the rest of the drag operation after scrolling has finished (or immediately
+ // if there will be no scrolling).
+ if (amount != 0f) {
+ scope.launch {
+ state.animateScrollBy(amount, tween(delayMillis = 250, durationMillis = 1000))
+ performDragAction()
+ }
+ } else {
+ performDragAction()
+ }
+ }
+ }
+
+ override fun onStarted() {
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
+ placeHolderIndex = contentListState.list.size - 1
+
+ // Use the width of the first item as the column width.
+ columnWidth =
+ state.layoutInfo.visibleItemsInfo.first().size.width +
+ state.layoutInfo.beforeContentPadding +
+ state.layoutInfo.afterContentPadding
}
- private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) }
+ override fun onMoved(event: DragAndDropEvent) {
+ dragOffset = event.toOffset()
+ scrollChannel.trySend(computeAutoscroll(dragOffset))
+ }
+
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return placeHolderIndex?.let { dropIndex ->
+ val widgetExtra = event.maybeWidgetExtra() ?: return false
+ val (componentName, user) = widgetExtra
+ if (componentName != null && user != null) {
+ // Placeholder isn't removed yet to allow the setting the right rank for items
+ // before adding in the new item.
+ contentListState.onSaveList(
+ newItemComponentName = componentName,
+ newItemUser = user,
+ newItemIndex = dropIndex,
+ )
+ return@let true
+ }
+ return false
+ } ?: false
+ }
+
+ override fun onEnded() {
+ placeHolderIndex = null
+ previousTargetItemKey = null
+ contentListState.list.remove(placeHolder)
+ }
+
+ override fun onExited() {
+ onEnded()
+ }
+
+ private fun performDragAction() {
+ val targetItem =
+ state.layoutInfo.visibleItemsInfo
+ .asSequence()
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ .firstItemAtOffset(dragOffset - contentOffset)
+
+ if (
+ targetItem != null &&
+ (!communalWidgetResizing() || targetItem.key != previousTargetItemKey)
+ ) {
+ if (communalWidgetResizing()) {
+ // Keep track of the previous target item, to avoid rapidly oscillating between
+ // items if the target item doesn't visually move as a result of the index change.
+ // In this case, even after the index changes, we'd still be colliding with the
+ // element, so it would be selected as the target item the next time this function
+ // runs again, which would trigger us to revert the index change we recently made.
+ previousTargetItemKey = targetItem.key
+ }
+
+ val scrollToIndex =
+ if (targetItem.index == state.firstVisibleItemIndex) {
+ placeHolderIndex
+ } else if (placeHolderIndex == state.firstVisibleItemIndex) {
+ targetItem.index
+ } else {
+ null
+ }
+
+ if (scrollToIndex != null) {
+ scope.launch {
+ state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
+ movePlaceholderTo(targetItem.index)
+ }
+ } else {
+ movePlaceholderTo(targetItem.index)
+ }
+
+ placeHolderIndex = targetItem.index
+ } else if (targetItem == null) {
+ previousTargetItemKey = null
+ }
+ }
+
+ private fun computeAutoscroll(dragOffset: Offset): Float {
+ val orientation = state.layoutInfo.orientation
+ val distanceFromStart =
+ if (orientation == Orientation.Horizontal) {
+ dragOffset.x
+ } else {
+ dragOffset.y
+ }
+ val distanceFromEnd =
+ if (orientation == Orientation.Horizontal) {
+ state.layoutInfo.viewportEndOffset - dragOffset.x
+ } else {
+ state.layoutInfo.viewportEndOffset - dragOffset.y
+ }
+
+ return when {
+ distanceFromEnd < autoScrollThreshold -> {
+ (columnWidth - state.layoutInfo.beforeContentPadding).toFloat()
+ }
+ distanceFromStart < autoScrollThreshold -> {
+ -(columnWidth - state.layoutInfo.afterContentPadding).toFloat()
+ }
+ else -> 0f
+ }
+ }
+
+ private fun movePlaceholderTo(index: Int) {
+ val currentIndex = contentListState.list.indexOf(placeHolder)
+ if (currentIndex != index) {
+ contentListState.swapItems(currentIndex, index)
+ }
+ }
}
+
+/**
+ * Parses and returns the intent extra associated with the widget that is dropped into the grid.
+ *
+ * Returns null if the drop event didn't include intent information.
+ */
+private fun DragAndDropEvent.maybeWidgetExtra(): WidgetPickerIntentUtils.WidgetExtra? {
+ val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
+ return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) }
+}
+
+private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index c972d3e3cf15..2a5addeb4951 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -19,7 +19,10 @@ package com.android.systemui.communal.ui.compose
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
@@ -37,13 +40,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import com.android.systemui.Flags.communalWidgetResizing
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
@@ -62,22 +68,22 @@ fun rememberGridDragDropState(
contentListState: ContentListState,
updateDragPositionForRemove: (boundingBox: IntRect) -> Boolean,
): GridDragDropState {
- val scope = rememberCoroutineScope()
+ val coroutineScope = rememberCoroutineScope()
+ val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+
val state =
remember(gridState, contentListState, updateDragPositionForRemove) {
GridDragDropState(
- state = gridState,
+ gridState = gridState,
contentListState = contentListState,
- scope = scope,
+ coroutineScope = coroutineScope,
+ autoScrollThreshold = autoScrollThreshold,
updateDragPositionForRemove = updateDragPositionForRemove,
)
}
- LaunchedEffect(state) {
- while (true) {
- val diff = state.scrollChannel.receive()
- gridState.scrollBy(diff)
- }
- }
+
+ LaunchedEffect(state) { state.processScrollRequests(coroutineScope) }
+
return state
}
@@ -89,36 +95,86 @@ fun rememberGridDragDropState(
* to remove the dragged item if condition met and call [ContentListState.onSaveList] to persist any
* change in ordering.
*/
-class GridDragDropState
-internal constructor(
- private val state: LazyGridState,
- private val contentListState: ContentListState,
- private val scope: CoroutineScope,
+class GridDragDropState(
+ val gridState: LazyGridState,
+ contentListState: ContentListState,
+ coroutineScope: CoroutineScope,
+ autoScrollThreshold: Float,
private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean,
) {
- var draggingItemKey by mutableStateOf<String?>(null)
- private set
+ private val dragDropState: GridDragDropStateInternal =
+ if (glanceableHubV2()) {
+ GridDragDropStateV2(
+ gridState = gridState,
+ contentListState = contentListState,
+ scope = coroutineScope,
+ autoScrollThreshold = autoScrollThreshold,
+ updateDragPositionForRemove = updateDragPositionForRemove,
+ )
+ } else {
+ GridDragDropStateV1(
+ gridState = gridState,
+ contentListState = contentListState,
+ scope = coroutineScope,
+ updateDragPositionForRemove = updateDragPositionForRemove,
+ )
+ }
- var isDraggingToRemove by mutableStateOf(false)
- private set
+ val draggingItemKey: String?
+ get() = dragDropState.draggingItemKey
- internal val scrollChannel = Channel<Float>()
+ val isDraggingToRemove: Boolean
+ get() = dragDropState.isDraggingToRemove
- private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero)
- private var draggingItemInitialOffset by mutableStateOf(Offset.Zero)
+ val draggingItemOffset: Offset
+ get() = dragDropState.draggingItemOffset
- private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1))
- private var spacerIndex: Int? = null
+ /**
+ * Called when dragging is initiated.
+ *
+ * @return {@code True} if dragging a grid item, {@code False} otherwise.
+ */
+ fun onDragStart(
+ offset: Offset,
+ screenWidth: Int,
+ layoutDirection: LayoutDirection,
+ contentOffset: Offset,
+ ): Boolean = dragDropState.onDragStart(offset, screenWidth, layoutDirection, contentOffset)
- private var previousTargetItemKey: Any? = null
+ fun onDragInterrupted() = dragDropState.onDragInterrupted()
+
+ fun onDrag(offset: Offset, layoutDirection: LayoutDirection) =
+ dragDropState.onDrag(offset, layoutDirection)
+
+ suspend fun processScrollRequests(coroutineScope: CoroutineScope) =
+ dragDropState.processScrollRequests(coroutineScope)
+}
+
+/**
+ * A private base class defining the API for handling drag-and-drop operations. There will be two
+ * implementations of this class: V1 for devices that do not have the glanceable_hub_v2 flag
+ * enabled, and V2 for devices that do have that flag enabled.
+ *
+ * TODO(b/400789179): Remove this class and the V1 implementation once glanceable_hub_v2 has
+ * shipped.
+ */
+private open class GridDragDropStateInternal(protected val state: LazyGridState) {
+ var draggingItemKey by mutableStateOf<String?>(null)
+ protected set
- internal val draggingItemOffset: Offset
+ var isDraggingToRemove by mutableStateOf(false)
+ protected set
+
+ var draggingItemDraggedDelta by mutableStateOf(Offset.Zero)
+ var draggingItemInitialOffset by mutableStateOf(Offset.Zero)
+
+ val draggingItemOffset: Offset
get() =
draggingItemLayoutInfo?.let { item ->
draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset()
} ?: Offset.Zero
- private val draggingItemLayoutInfo: LazyGridItemInfo?
+ val draggingItemLayoutInfo: LazyGridItemInfo?
get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == draggingItemKey }
/**
@@ -126,7 +182,45 @@ internal constructor(
*
* @return {@code True} if dragging a grid item, {@code False} otherwise.
*/
- internal fun onDragStart(
+ open fun onDragStart(
+ offset: Offset,
+ screenWidth: Int,
+ layoutDirection: LayoutDirection,
+ contentOffset: Offset,
+ ): Boolean = false
+
+ open fun onDragInterrupted() = Unit
+
+ open fun onDrag(offset: Offset, layoutDirection: LayoutDirection) = Unit
+
+ open suspend fun processScrollRequests(coroutineScope: CoroutineScope) = Unit
+}
+
+/**
+ * The V1 implementation of GridDragDropStateInternal to be used when the glanceable_hub_v2 flag is
+ * disabled.
+ */
+private class GridDragDropStateV1(
+ val gridState: LazyGridState,
+ private val contentListState: ContentListState,
+ private val scope: CoroutineScope,
+ private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean,
+) : GridDragDropStateInternal(gridState) {
+ private val scrollChannel = Channel<Float>()
+
+ private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1))
+ private var spacerIndex: Int? = null
+
+ private var previousTargetItemKey: Any? = null
+
+ override suspend fun processScrollRequests(coroutineScope: CoroutineScope) {
+ while (true) {
+ val diff = scrollChannel.receive()
+ state.scrollBy(diff)
+ }
+ }
+
+ override fun onDragStart(
offset: Offset,
screenWidth: Int,
layoutDirection: LayoutDirection,
@@ -162,7 +256,7 @@ internal constructor(
return false
}
- internal fun onDragInterrupted() {
+ override fun onDragInterrupted() {
draggingItemKey?.let {
if (isDraggingToRemove) {
contentListState.onRemove(
@@ -185,7 +279,7 @@ internal constructor(
}
}
- internal fun onDrag(offset: Offset, layoutDirection: LayoutDirection) {
+ override fun onDrag(offset: Offset, layoutDirection: LayoutDirection) {
// Adjust offset to match the layout direction
draggingItemDraggedDelta +=
Offset(offset.x.directional(LayoutDirection.Ltr, layoutDirection), offset.y)
@@ -282,6 +376,249 @@ internal constructor(
}
}
+/**
+ * The V2 implementation of GridDragDropStateInternal to be used when the glanceable_hub_v2 flag is
+ * enabled.
+ */
+private class GridDragDropStateV2(
+ val gridState: LazyGridState,
+ private val contentListState: ContentListState,
+ private val scope: CoroutineScope,
+ private val autoScrollThreshold: Float,
+ private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean,
+) : GridDragDropStateInternal(gridState) {
+
+ private val scrollChannel = Channel<Float>(Channel.UNLIMITED)
+
+ // Used to keep track of the dragging item during scrolling (because it might be off screen
+ // and no longer in the list of visible items).
+ private var draggingItemWhileScrolling: LazyGridItemInfo? by mutableStateOf(null)
+
+ private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1))
+ private var spacerIndex: Int? = null
+
+ private var previousTargetItemKey: Any? = null
+
+ // Basically, the location of the user's finger on the screen.
+ private var currentDragPositionOnScreen by mutableStateOf(Offset.Zero)
+ // The offset of the grid from the top of the screen.
+ private var contentOffset = Offset.Zero
+
+ // The width of one column in the grid (needed in order to auto-scroll one column at a time).
+ private var columnWidth = 0
+
+ override suspend fun processScrollRequests(coroutineScope: CoroutineScope) {
+ while (true) {
+ val amount = scrollChannel.receive()
+
+ if (state.isScrollInProgress) {
+ // Ignore overscrolling if a scroll is already in progress (but we still want to
+ // consume the scroll event so that we don't end up processing a bunch of old
+ // events after scrolling has finished).
+ continue
+ }
+
+ // We perform the rest of the drag action after scrolling has finished (or immediately
+ // if there will be no scrolling).
+ if (amount != 0f) {
+ coroutineScope.launch {
+ state.animateScrollBy(amount, tween(delayMillis = 250, durationMillis = 1000))
+ performDragAction()
+ }
+ } else {
+ performDragAction()
+ }
+ }
+ }
+
+ override fun onDragStart(
+ offset: Offset,
+ screenWidth: Int,
+ layoutDirection: LayoutDirection,
+ contentOffset: Offset,
+ ): Boolean {
+ val normalizedOffset =
+ Offset(
+ if (layoutDirection == LayoutDirection.Ltr) offset.x else screenWidth - offset.x,
+ offset.y,
+ )
+
+ currentDragPositionOnScreen = normalizedOffset
+ this.contentOffset = contentOffset
+
+ state.layoutInfo.visibleItemsInfo
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ // grid item offset is based off grid content container so we need to deduct
+ // before content padding from the initial pointer position
+ .firstItemAtOffset(normalizedOffset - contentOffset)
+ ?.apply {
+ draggingItemKey = key as String
+ draggingItemWhileScrolling = this
+ draggingItemInitialOffset = this.offset.toOffset()
+ columnWidth =
+ this.size.width +
+ state.layoutInfo.beforeContentPadding +
+ state.layoutInfo.afterContentPadding
+ // Add a spacer after the last widget if it is larger than the dragging widget.
+ // This allows overscrolling, enabling the dragging widget to be placed beyond it.
+ val lastWidget = contentListState.list.lastOrNull { it.isWidgetContent() }
+ if (
+ lastWidget != null &&
+ draggingItemLayoutInfo != null &&
+ lastWidget.size.span > draggingItemLayoutInfo!!.span
+ ) {
+ contentListState.list.add(spacer)
+ spacerIndex = contentListState.list.size - 1
+ }
+ return true
+ }
+
+ return false
+ }
+
+ override fun onDragInterrupted() {
+ draggingItemKey?.let {
+ if (isDraggingToRemove) {
+ contentListState.onRemove(
+ contentListState.list.indexOfFirst { it.key == draggingItemKey }
+ )
+ isDraggingToRemove = false
+ updateDragPositionForRemove(IntRect.Zero)
+ }
+ // persist list editing changes on dragging ends
+ contentListState.onSaveList()
+ draggingItemKey = null
+ }
+ previousTargetItemKey = null
+ draggingItemDraggedDelta = Offset.Zero
+ draggingItemInitialOffset = Offset.Zero
+ currentDragPositionOnScreen = Offset.Zero
+ draggingItemWhileScrolling = null
+ // Remove spacer, if any, when a drag gesture finishes.
+ spacerIndex?.let {
+ contentListState.list.removeAt(it)
+ spacerIndex = null
+ }
+ }
+
+ override fun onDrag(offset: Offset, layoutDirection: LayoutDirection) {
+ // Adjust offset to match the layout direction
+ val delta = Offset(offset.x.directional(LayoutDirection.Ltr, layoutDirection), offset.y)
+ draggingItemDraggedDelta += delta
+ currentDragPositionOnScreen += delta
+
+ scrollChannel.trySend(computeAutoscroll(currentDragPositionOnScreen))
+ }
+
+ fun performDragAction() {
+ val draggingItem = draggingItemLayoutInfo ?: draggingItemWhileScrolling
+ if (draggingItem == null) {
+ return
+ }
+
+ val draggingBoundingBox =
+ IntRect(draggingItem.offset + draggingItemOffset.round(), draggingItem.size)
+ val curDragPositionInGrid = (currentDragPositionOnScreen - contentOffset)
+
+ val targetItem =
+ if (communalWidgetResizing()) {
+ val lastVisibleItemIndex = state.layoutInfo.visibleItemsInfo.last().index
+ state.layoutInfo.visibleItemsInfo.findLast(
+ fun(item): Boolean {
+ val itemBoundingBox = IntRect(item.offset, item.size)
+ return draggingItemKey != item.key &&
+ contentListState.isItemEditable(item.index) &&
+ itemBoundingBox.contains(curDragPositionInGrid.round()) &&
+ // If we swap with the last visible item, and that item doesn't fit
+ // in the gap created by moving the current item, then the current item
+ // will get placed after the last visible item. In this case, it gets
+ // placed outside of the viewport. We avoid this here, so the user
+ // has to scroll first before the swap can happen.
+ (item.index != lastVisibleItemIndex || item.span <= draggingItem.span)
+ }
+ )
+ } else {
+ state.layoutInfo.visibleItemsInfo
+ .asSequence()
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ .filter { item -> draggingItem.index != item.index }
+ .firstItemAtOffset(curDragPositionInGrid)
+ }
+
+ if (
+ targetItem != null &&
+ (!communalWidgetResizing() || targetItem.key != previousTargetItemKey)
+ ) {
+ val scrollToIndex =
+ if (targetItem.index == state.firstVisibleItemIndex) {
+ draggingItem.index
+ } else if (draggingItem.index == state.firstVisibleItemIndex) {
+ targetItem.index
+ } else {
+ null
+ }
+ if (communalWidgetResizing()) {
+ // Keep track of the previous target item, to avoid rapidly oscillating between
+ // items if the target item doesn't visually move as a result of the index change.
+ // In this case, even after the index changes, we'd still be colliding with the
+ // element, so it would be selected as the target item the next time this function
+ // runs again, which would trigger us to revert the index change we recently made.
+ previousTargetItemKey = targetItem.key
+ }
+ if (scrollToIndex != null) {
+ scope.launch {
+ // this is needed to neutralize automatic keeping the first item first.
+ state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
+ contentListState.swapItems(draggingItem.index, targetItem.index)
+ }
+ } else {
+ contentListState.swapItems(draggingItem.index, targetItem.index)
+ }
+ draggingItemWhileScrolling = targetItem
+ isDraggingToRemove = false
+ } else if (targetItem == null) {
+ isDraggingToRemove = checkForRemove(draggingBoundingBox)
+ previousTargetItemKey = null
+ }
+ }
+
+ /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled. */
+ private fun computeAutoscroll(dragOffset: Offset): Float {
+ val orientation = state.layoutInfo.orientation
+ val distanceFromStart =
+ if (orientation == Orientation.Horizontal) {
+ dragOffset.x
+ } else {
+ dragOffset.y
+ }
+ val distanceFromEnd =
+ if (orientation == Orientation.Horizontal) {
+ state.layoutInfo.viewportEndOffset - dragOffset.x
+ } else {
+ state.layoutInfo.viewportEndOffset - dragOffset.y
+ }
+
+ return when {
+ distanceFromEnd < autoScrollThreshold -> {
+ (columnWidth - state.layoutInfo.beforeContentPadding).toFloat()
+ }
+ distanceFromStart < autoScrollThreshold -> {
+ -(columnWidth - state.layoutInfo.afterContentPadding).toFloat()
+ }
+ else -> 0f
+ }
+ }
+
+ /** Calls the callback with the updated drag position and returns whether to remove the item. */
+ private fun checkForRemove(draggingItemBoundingBox: IntRect): Boolean {
+ return if (draggingItemDraggedDelta.y < 0) {
+ updateDragPositionForRemove(draggingItemBoundingBox)
+ } else {
+ false
+ }
+ }
+}
+
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
layoutDirection: LayoutDirection,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
index 9eb78e14ab4e..b1afb161f33d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
@@ -16,7 +16,7 @@
package com.android.systemui.compose.modifiers
-import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
@@ -26,7 +26,11 @@ import androidx.compose.ui.semantics.testTagsAsResourceId
* Set a test tag on this node so that it is associated with [resId]. This node will then be
* accessible by integration tests using `sysuiResSelector(resId)`.
*/
-@OptIn(ExperimentalComposeUiApi::class)
+@Stable
fun Modifier.sysuiResTag(resId: String): Modifier {
- return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
+ // TODO(b/372412931): Only compose the semantics modifier once, at the root of the SystemUI
+ // window.
+ return this.then(TestTagAsResourceIdModifier).testTag("com.android.systemui:id/$resId")
}
+
+private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 60c017227334..7782705d4c61 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -73,7 +73,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.ContentScope
-import com.android.compose.modifiers.fadingBackground
+import com.android.compose.modifiers.animatedBackground
import com.android.compose.theme.colorAttr
import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.Expandable
@@ -141,7 +141,7 @@ fun FooterActions(
mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null)
}
var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
- var power by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
+ var power by remember { mutableStateOf(viewModel.initialPower()) }
LaunchedEffect(
context,
@@ -172,8 +172,8 @@ fun FooterActions(
val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
val backgroundModifier =
remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) {
- Modifier.fadingBackground(
- backgroundColor,
+ Modifier.animatedBackground(
+ { backgroundColor },
backgroundAlphaValue,
RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
)
@@ -218,23 +218,19 @@ fun FooterActions(
}
val useModifierBasedExpandable = remember { QSComposeFragment.isEnabled }
- security?.let { SecurityButton(it, useModifierBasedExpandable, Modifier.weight(1f)) }
- foregroundServices?.let { ForegroundServicesButton(it, useModifierBasedExpandable) }
- userSwitcher?.let {
- IconButton(
- it,
- useModifierBasedExpandable,
- Modifier.sysuiResTag("multi_user_switch"),
- )
- }
+ SecurityButton({ security }, useModifierBasedExpandable, Modifier.weight(1f))
+ ForegroundServicesButton({ foregroundServices }, useModifierBasedExpandable)
IconButton(
- viewModel.settings,
+ { userSwitcher },
+ useModifierBasedExpandable,
+ Modifier.sysuiResTag("multi_user_switch"),
+ )
+ IconButton(
+ { viewModel.settings },
useModifierBasedExpandable,
Modifier.sysuiResTag("settings_button_container"),
)
- power?.let {
- IconButton(it, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite"))
- }
+ IconButton({ power }, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite"))
}
}
}
@@ -242,10 +238,11 @@ fun FooterActions(
/** The security button. */
@Composable
private fun SecurityButton(
- model: FooterActionsSecurityButtonViewModel,
+ model: () -> FooterActionsSecurityButtonViewModel?,
useModifierBasedExpandable: Boolean,
modifier: Modifier = Modifier,
) {
+ val model = model() ?: return
val onClick: ((Expandable) -> Unit)? =
model.onClick?.let { onClick ->
val context = LocalContext.current
@@ -265,9 +262,10 @@ private fun SecurityButton(
/** The foreground services button. */
@Composable
private fun RowScope.ForegroundServicesButton(
- model: FooterActionsForegroundServicesButtonViewModel,
+ model: () -> FooterActionsForegroundServicesButtonViewModel?,
useModifierBasedExpandable: Boolean,
) {
+ val model = model() ?: return
if (model.displayText) {
TextButton(
Icon.Resource(R.drawable.ic_info_outline, contentDescription = null),
@@ -291,6 +289,17 @@ private fun RowScope.ForegroundServicesButton(
/** A button with an icon. */
@Composable
fun IconButton(
+ model: () -> FooterActionsButtonViewModel?,
+ useModifierBasedExpandable: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val model = model() ?: return
+ IconButton(model, useModifierBasedExpandable, modifier)
+}
+
+/** A button with an icon. */
+@Composable
+fun IconButton(
model: FooterActionsButtonViewModel,
useModifierBasedExpandable: Boolean,
modifier: Modifier = Modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 061fdd99eb1b..0a711487ccb1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -356,7 +356,8 @@ private fun ContentScope.QuickSettingsScene(
modifier = Modifier.padding(horizontal = 16.dp),
)
}
- else -> CollapsedShadeHeader(viewModel = headerViewModel)
+ else ->
+ CollapsedShadeHeader(viewModel = headerViewModel, isSplitShade = false)
}
Spacer(modifier = Modifier.height(16.dp))
// This view has its own horizontal padding
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 547461e5faf2..8f0fb20cef36 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -49,7 +49,10 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.modifiers.thenIf
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.brightness.ui.compose.ContainerColors
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
@@ -255,11 +258,18 @@ fun ContentScope.QuickSettingsLayout(
modifier = Modifier.padding(horizontal = QuickSettingsShade.Dimensions.Padding),
)
- BrightnessSliderContainer(
- viewModel = viewModel.brightnessSliderViewModel,
- containerColor = OverlayShade.Colors.PanelBackground,
- modifier = Modifier.systemGestureExclusionInShade().fillMaxWidth(),
- )
+ Box(
+ Modifier.systemGestureExclusionInShade(
+ enabled = { layoutState.transitionState is TransitionState.Idle }
+ )
+ ) {
+ BrightnessSliderContainer(
+ viewModel = viewModel.brightnessSliderViewModel,
+ containerColors =
+ ContainerColors.singleColor(OverlayShade.Colors.PanelBackground),
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
Box {
GridAnchor()
@@ -289,18 +299,20 @@ object QuickSettingsShade {
* right.
*/
@Composable
- fun Modifier.systemGestureExclusionInShade(): Modifier {
+ fun Modifier.systemGestureExclusionInShade(enabled: () -> Boolean): Modifier {
val density = LocalDensity.current
- return systemGestureExclusion { layoutCoordinates ->
- val sidePadding = with(density) { Dimensions.Padding.toPx() }
- Rect(
- offset = Offset(x = -sidePadding, y = 0f),
- size =
- Size(
- width = layoutCoordinates.size.width.toFloat() + 2 * sidePadding,
- height = layoutCoordinates.size.height.toFloat(),
- ),
- )
+ return thenIf(enabled()) {
+ Modifier.systemGestureExclusion { layoutCoordinates ->
+ val sidePadding = with(density) { Dimensions.Padding.toPx() }
+ Rect(
+ offset = Offset(x = -sidePadding, y = 0f),
+ size =
+ Size(
+ width = layoutCoordinates.size.width.toFloat() + 2 * sidePadding,
+ height = layoutCoordinates.size.height.toFloat(),
+ ),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 0c502e6f492d..0daef465bf1f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -56,6 +56,7 @@ import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.isFullWidthShade
import javax.inject.Provider
@@ -100,9 +101,13 @@ fun SceneContainer(
rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
val hapticFeedback = LocalHapticFeedback.current
+ val shadeExpansionMotion = OverlayShade.rememberShadeExpansionMotion(isFullWidthShade())
val sceneTransitions =
- remember(hapticFeedback) {
- transitionsBuilder.build(viewModel.hapticsViewModel.getRevealHaptics(hapticFeedback))
+ remember(hapticFeedback, shadeExpansionMotion) {
+ transitionsBuilder.build(
+ shadeExpansionMotion,
+ viewModel.hapticsViewModel.getRevealHaptics(hapticFeedback),
+ )
}
val state =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 7fe19fe70a25..2f5a0306c84f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -6,6 +6,7 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.transitions
import com.android.internal.jank.Cuj
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
@@ -48,7 +49,10 @@ import com.android.systemui.shade.ui.composable.Shade
* Please keep the list sorted alphabetically.
*/
class SceneContainerTransitions : SceneContainerTransitionsBuilder {
- override fun build(revealHaptics: ContainerRevealHaptics): SceneTransitions {
+ override fun build(
+ shadeExpansionMotion: VerticalExpandContainerSpec,
+ revealHaptics: ContainerRevealHaptics,
+ ): SceneTransitions {
return transitions {
interruptionHandler = DefaultInterruptionHandler
@@ -201,13 +205,19 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder {
Overlays.NotificationsShade,
cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
) {
- toNotificationsShadeTransition(revealHaptics = revealHaptics)
+ toNotificationsShadeTransition(
+ shadeExpansionMotion = shadeExpansionMotion,
+ revealHaptics = revealHaptics,
+ )
}
to(
Overlays.QuickSettingsShade,
cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
) {
- toQuickSettingsShadeTransition(revealHaptics = revealHaptics)
+ toQuickSettingsShadeTransition(
+ shadeExpansionMotion = shadeExpansionMotion,
+ revealHaptics = revealHaptics,
+ )
}
from(
Scenes.Gone,
@@ -215,7 +225,11 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder {
key = SlightlyFasterShadeCollapse,
cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
) {
- toNotificationsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
+ toNotificationsShadeTransition(
+ durationScale = 0.9,
+ shadeExpansionMotion = shadeExpansionMotion,
+ revealHaptics = revealHaptics,
+ )
}
from(
Scenes.Gone,
@@ -223,7 +237,11 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder {
key = SlightlyFasterShadeCollapse,
cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
) {
- toQuickSettingsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
+ toQuickSettingsShadeTransition(
+ durationScale = 0.9,
+ shadeExpansionMotion = shadeExpansionMotion,
+ revealHaptics = revealHaptics,
+ )
}
from(
Scenes.Lockscreen,
@@ -231,7 +249,11 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder {
key = SlightlyFasterShadeCollapse,
cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
) {
- toNotificationsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
+ toNotificationsShadeTransition(
+ durationScale = 0.9,
+ shadeExpansionMotion = shadeExpansionMotion,
+ revealHaptics = revealHaptics,
+ )
}
from(
Scenes.Lockscreen,
@@ -239,7 +261,11 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder {
key = SlightlyFasterShadeCollapse,
cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
) {
- toQuickSettingsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
+ toQuickSettingsShadeTransition(
+ durationScale = 0.9,
+ shadeExpansionMotion = shadeExpansionMotion,
+ revealHaptics = revealHaptics,
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt
index 13d3456baa74..4c9c23ad9f9f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitionsBuilder.kt
@@ -19,6 +19,7 @@ package com.android.systemui.scene.ui.composable
import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.transitions
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
/**
* Builder of the comprehensive definition of all transitions between scenes and overlays in the
@@ -27,7 +28,10 @@ import com.android.compose.animation.scene.transitions
interface SceneContainerTransitionsBuilder {
/** Build the [SceneContainer] transitions spec. */
- fun build(revealHaptics: ContainerRevealHaptics): SceneTransitions
+ fun build(
+ shadeExpansionMotion: VerticalExpandContainerSpec,
+ revealHaptics: ContainerRevealHaptics,
+ ): SceneTransitions
}
/**
@@ -37,5 +41,8 @@ interface SceneContainerTransitionsBuilder {
class ConstantSceneContainerTransitionsBuilder(
private val transitions: SceneTransitions = transitions { /* No transitions */ }
) : SceneContainerTransitionsBuilder {
- override fun build(revealHaptics: ContainerRevealHaptics): SceneTransitions = transitions
+ override fun build(
+ shadeExpansionMotion: VerticalExpandContainerSpec,
+ revealHaptics: ContainerRevealHaptics,
+ ): SceneTransitions = transitions
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 722af6ae4f34..85aad9b087f1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -20,6 +20,7 @@ import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.reveal.verticalContainerReveal
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
import com.android.systemui.notifications.ui.composable.NotificationsShade
import com.android.systemui.scene.shared.model.Overlays
@@ -28,6 +29,7 @@ import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toNotificationsShadeTransition(
durationScale: Double = 1.0,
+ shadeExpansionMotion: VerticalExpandContainerSpec,
revealHaptics: ContainerRevealHaptics,
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
@@ -38,7 +40,7 @@ fun TransitionBuilder.toNotificationsShadeTransition(
elevateInContent = Overlays.NotificationsShade,
)
- verticalContainerReveal(NotificationsShade.Elements.Panel, revealHaptics)
+ verticalContainerReveal(NotificationsShade.Elements.Panel, shadeExpansionMotion, revealHaptics)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
fractionRange(start = .5f) { fade(NotificationsShade.Elements.StatusBar) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index 3cce99740e47..8f0447d05036 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -20,17 +20,19 @@ import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.reveal.verticalContainerReveal
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
import com.android.systemui.qs.ui.composable.QuickSettingsShade
import com.android.systemui.shade.ui.composable.OverlayShade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toQuickSettingsShadeTransition(
durationScale: Double = 1.0,
+ shadeExpansionMotion: VerticalExpandContainerSpec,
revealHaptics: ContainerRevealHaptics,
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- verticalContainerReveal(QuickSettingsShade.Elements.Panel, revealHaptics)
+ verticalContainerReveal(QuickSettingsShade.Elements.Panel, shadeExpansionMotion, revealHaptics)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
fractionRange(start = .5f) { fade(QuickSettingsShade.Elements.StatusBar) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 7e7b6297406e..068218a0053a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -37,24 +37,26 @@ import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
import androidx.compose.foundation.layout.waterfall
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.overscroll
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
+import com.android.mechanics.behavior.verticalExpandContainerBackground
import com.android.systemui.res.R
-import androidx.compose.ui.unit.Dp
+import com.android.systemui.shade.ui.composable.OverlayShade.rememberShadeExpansionMotion
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
@@ -110,23 +112,15 @@ private fun ContentScope.Panel(
) {
Box(
modifier =
- modifier.clip(OverlayShade.Shapes.RoundedCornerPanel).disableSwipesWhenScrolling()
+ modifier
+ .disableSwipesWhenScrolling()
+ .verticalExpandContainerBackground(
+ backgroundColor = OverlayShade.Colors.PanelBackground,
+ spec = rememberShadeExpansionMotion(isFullWidthShade()),
+ )
) {
- Spacer(
- modifier =
- Modifier.element(OverlayShade.Elements.PanelBackground)
- .matchParentSize()
- .background(
- color = OverlayShade.Colors.PanelBackground,
- shape = OverlayShade.Shapes.RoundedCornerPanel,
- )
- )
-
Column {
header?.invoke()
-
- // This content is intentionally rendered as a separate element from the background in
- // order to allow for more flexibility when defining transitions.
content()
}
}
@@ -192,8 +186,6 @@ object OverlayShade {
contentPicker = LowestZIndexContentPicker,
placeAllCopies = true,
)
- val PanelBackground =
- ElementKey("OverlayShadePanelBackground", contentPicker = LowestZIndexContentPicker)
}
object Colors {
@@ -205,13 +197,15 @@ object OverlayShade {
object Dimensions {
val PanelCornerRadius: Dp
@Composable
- @ReadOnlyComposable get() =
- dimensionResource(R.dimen.overlay_shade_panel_shape_radius)
+ @ReadOnlyComposable
+ get() = dimensionResource(R.dimen.overlay_shade_panel_shape_radius)
}
- object Shapes {
- val RoundedCornerPanel: RoundedCornerShape
- @Composable
- @ReadOnlyComposable get() = RoundedCornerShape(Dimensions.PanelCornerRadius)
+ @Composable
+ fun rememberShadeExpansionMotion(isFullWidth: Boolean): VerticalExpandContainerSpec {
+ val radius = Dimensions.PanelCornerRadius
+ return remember(radius, isFullWidth) {
+ VerticalExpandContainerSpec(isFloating = !isFullWidth, radius = radius)
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 23baeacd76ec..86c8fc34a63c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -127,6 +127,7 @@ object ShadeHeader {
@Composable
fun ContentScope.CollapsedShadeHeader(
viewModel: ShadeHeaderViewModel,
+ isSplitShade: Boolean,
modifier: Modifier = Modifier,
) {
val cutoutLocation = LocalDisplayCutout.current.location
@@ -141,8 +142,6 @@ fun ContentScope.CollapsedShadeHeader(
}
}
- val isShadeLayoutWide = viewModel.isShadeLayoutWide
-
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
// This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
@@ -154,7 +153,7 @@ fun ContentScope.CollapsedShadeHeader(
horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
- Clock(scale = 1f, onClick = viewModel::onClockClicked)
+ Clock(onClick = viewModel::onClockClicked)
VariableDayDate(
longerDateText = viewModel.longerDateText,
shorterDateText = viewModel.shorterDateText,
@@ -184,11 +183,11 @@ fun ContentScope.CollapsedShadeHeader(
Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
.padding(horizontal = horizontalPadding),
) {
- if (isShadeLayoutWide) {
+ if (isSplitShade) {
ShadeCarrierGroup(viewModel = viewModel)
}
SystemIconChip(
- onClick = viewModel::onSystemIconChipClicked.takeIf { isShadeLayoutWide }
+ onClick = viewModel::onSystemIconChipClicked.takeIf { isSplitShade }
) {
StatusIcons(
viewModel = viewModel,
@@ -233,13 +232,11 @@ fun ContentScope.ExpandedShadeHeader(
.defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight),
) {
Box(modifier = Modifier.fillMaxWidth()) {
- Box {
- Clock(
- scale = 2.57f,
- onClick = viewModel::onClockClicked,
- modifier = Modifier.align(Alignment.CenterStart),
- )
- }
+ Clock(
+ onClick = viewModel::onClockClicked,
+ modifier = Modifier.align(Alignment.CenterStart),
+ scale = 2.57f,
+ )
Box(
modifier =
Modifier.element(ShadeHeader.Elements.ShadeCarrierGroup).fillMaxWidth()
@@ -291,8 +288,6 @@ fun ContentScope.OverlayShadeHeader(
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
- val isShadeLayoutWide = viewModel.isShadeLayoutWide
-
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
// This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
@@ -301,16 +296,15 @@ fun ContentScope.OverlayShadeHeader(
startContent = {
Row(
verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
val chipHighlight = viewModel.notificationsChipHighlight
- if (isShadeLayoutWide) {
+ if (viewModel.showClock) {
Clock(
- scale = 1f,
onClick = viewModel::onClockClicked,
modifier = Modifier.padding(horizontal = 4.dp),
)
- Spacer(modifier = Modifier.width(5.dp))
}
NotificationsChip(
onClick = viewModel::onNotificationIconChipClicked,
@@ -437,7 +431,11 @@ private fun CutoutAwareShadeHeader(
}
@Composable
-private fun ContentScope.Clock(scale: Float, onClick: () -> Unit, modifier: Modifier = Modifier) {
+private fun ContentScope.Clock(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ scale: Float = 1f,
+) {
val layoutDirection = LocalLayoutDirection.current
ElementWithValues(key = ShadeHeader.Elements.Clock, modifier = modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 5040490da8f6..885d34fb95c9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -56,11 +56,11 @@ import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
+import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
@@ -68,6 +68,7 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateContentDpAsState
+import com.android.compose.animation.scene.animateContentFloatAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.padding
@@ -223,9 +224,6 @@ private fun ContentScope.ShadeScene(
viewModel = viewModel,
headerViewModel = headerViewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
mediaHost = qqsMediaHost,
modifier = modifier,
@@ -253,9 +251,6 @@ private fun ContentScope.SingleShade(
viewModel: ShadeSceneContentViewModel,
headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
@@ -340,6 +335,7 @@ private fun ContentScope.SingleShade(
content = {
CollapsedShadeHeader(
viewModel = headerViewModel,
+ isSplitShade = false,
modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
)
@@ -434,15 +430,13 @@ private fun ContentScope.SplitShade(
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
val tileSquishiness by
- animateSceneFloatAsState(
+ animateContentFloatAsState(
value = 1f,
key = QuickSettings.SharedValues.TilesSquishiness,
canOverflow = false,
)
val unfoldTranslationXForStartSide by
viewModel.unfoldTranslationX(isOnStartSide = true).collectAsStateWithLifecycle(0f)
- val unfoldTranslationXForEndSide by
- viewModel.unfoldTranslationX(isOnStartSide = false).collectAsStateWithLifecycle(0f)
val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
@@ -512,6 +506,7 @@ private fun ContentScope.SplitShade(
Column(modifier = Modifier.fillMaxSize()) {
CollapsedShadeHeader(
viewModel = headerViewModel,
+ isSplitShade = true,
modifier =
Modifier.then(brightnessMirrorShowingModifier)
.padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 1360611ed814..024ca22069ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -68,7 +68,7 @@ internal class DraggableHandler(
return layoutImpl.swipeDetector.detectSwipe(change)
}
- override fun shouldConsumeNestedScroll(sign: Float): Boolean {
+ override fun shouldConsumeNestedPostScroll(sign: Float): Boolean {
return this.enabled()
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 404f1b217026..22688d310b44 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -102,6 +102,7 @@ interface SceneTransitionLayoutScope<out CS : ContentScope> {
key: SceneKey,
userActions: Map<UserAction, UserActionResult> = emptyMap(),
effectFactory: OverscrollFactory? = null,
+ alwaysCompose: Boolean = false,
content: @Composable CS.() -> Unit,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index e3c4eb0f8bea..4da83c3a6fc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -207,6 +207,9 @@ internal class SceneTransitionLayoutImpl(
private val nestedScrollDispatcher = NestedScrollDispatcher()
private val nestedScrollConnection = object : NestedScrollConnection {}
+ // TODO(b/399825091): Remove this.
+ private var scenesToAlwaysCompose: MutableList<Scene>? = null
+
init {
updateContents(builder, layoutDirection, defaultEffectFactory)
@@ -312,6 +315,7 @@ internal class SceneTransitionLayoutImpl(
key: SceneKey,
userActions: Map<UserAction, UserActionResult>,
effectFactory: OverscrollFactory?,
+ alwaysCompose: Boolean,
content: @Composable InternalContentScope.() -> Unit,
) {
require(!overlaysDefined) { "all scenes must be defined before overlays" }
@@ -324,6 +328,10 @@ internal class SceneTransitionLayoutImpl(
Content.calculateGlobalZIndex(parentZIndex, ++zIndex, ancestors.size)
val factory = effectFactory ?: defaultEffectFactory
if (scene != null) {
+ check(alwaysCompose == scene.alwaysCompose) {
+ "scene.alwaysCompose can not change"
+ }
+
// Update an existing scene.
scene.content = content
scene.userActions = resolvedUserActions
@@ -332,7 +340,7 @@ internal class SceneTransitionLayoutImpl(
scene.maybeUpdateEffects(factory)
} else {
// New scene.
- scenes[key] =
+ val scene =
Scene(
key,
this@SceneTransitionLayoutImpl,
@@ -341,7 +349,16 @@ internal class SceneTransitionLayoutImpl(
zIndex.toFloat(),
globalZIndex,
factory,
+ alwaysCompose,
)
+
+ scenes[key] = scene
+
+ if (alwaysCompose) {
+ (scenesToAlwaysCompose
+ ?: mutableListOf<Scene>().also { scenesToAlwaysCompose = it })
+ .add(scene)
+ }
}
}
@@ -470,22 +487,24 @@ internal class SceneTransitionLayoutImpl(
@Composable
private fun Scenes() {
- scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+ scenesToCompose().fastForEach { (scene, isInvisible) ->
+ key(scene.key) { scene.Content(isInvisible = isInvisible) }
+ }
}
- private fun scenesToCompose(): List<Scene> {
+ private fun scenesToCompose(): List<SceneToCompose> {
val transitions = state.currentTransitions
- return if (transitions.isEmpty()) {
- listOf(scene(state.transitionState.currentScene))
- } else {
- buildList {
- val visited = mutableSetOf<SceneKey>()
- fun maybeAdd(sceneKey: SceneKey) {
- if (visited.add(sceneKey)) {
- add(scene(sceneKey))
- }
+ return buildList {
+ val visited = mutableSetOf<SceneKey>()
+ fun maybeAdd(sceneKey: SceneKey, isInvisible: Boolean = false) {
+ if (visited.add(sceneKey)) {
+ add(SceneToCompose(scene(sceneKey), isInvisible))
}
+ }
+ if (transitions.isEmpty()) {
+ maybeAdd(state.transitionState.currentScene)
+ } else {
// Compose the new scene we are going to first.
transitions.fastForEachReversed { transition ->
when (transition) {
@@ -504,9 +523,13 @@ internal class SceneTransitionLayoutImpl(
// Make sure that the current scene is always composed.
maybeAdd(transitions.last().currentScene)
}
+
+ scenesToAlwaysCompose?.fastForEach { maybeAdd(it.key, isInvisible = true) }
}
}
+ private data class SceneToCompose(val scene: Scene, val isInvisible: Boolean)
+
@Composable
private fun BoxScope.Overlays() {
val overlaysOrderedByZIndex = overlaysToComposeOrderedByZIndex()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 149a9e7c4705..90bf92ae1dd0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -32,11 +32,17 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.Ancestor
import com.android.compose.animation.scene.AnimatedState
import com.android.compose.animation.scene.ContentKey
@@ -67,8 +73,8 @@ import com.android.compose.gesture.NestedScrollControlState
import com.android.compose.gesture.NestedScrollableBound
import com.android.compose.gesture.nestedScrollController
import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.graphics.ContainerNode
import com.android.compose.ui.graphics.ContainerState
-import com.android.compose.ui.graphics.container
import kotlin.math.pow
/** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */
@@ -154,26 +160,17 @@ internal sealed class Content(
@SuppressLint("NotConstructor")
@Composable
- fun Content(modifier: Modifier = Modifier) {
+ fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) {
// If this content has a custom factory, provide it to the content so that the factory is
// automatically used when calling rememberOverscrollEffect().
+ val isElevationPossible =
+ layoutImpl.state.isElevationPossible(content = key, element = null)
Box(
- modifier
- .zIndex(zIndex)
- .approachLayout(
- isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
- ) { measurable, constraints ->
- // TODO(b/353679003): Use the ModifierNode API to set this *before* the
- // approach
- // pass is started.
- targetSize = lookaheadSize
- val placeable = measurable.measure(constraints)
- layout(placeable.width, placeable.height) { placeable.place(0, 0) }
- }
- .thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) {
- Modifier.container(containerState)
- }
- .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
+ modifier.then(ContentElement(this, isElevationPossible, isInvisible)).thenIf(
+ layoutImpl.implicitTestTags
+ ) {
+ Modifier.testTag(key.testTag)
+ }
) {
CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) {
scope.content()
@@ -192,6 +189,72 @@ internal sealed class Content(
}
}
+private data class ContentElement(
+ private val content: Content,
+ private val isElevationPossible: Boolean,
+ private val isInvisible: Boolean,
+) : ModifierNodeElement<ContentNode>() {
+ override fun create(): ContentNode = ContentNode(content, isElevationPossible, isInvisible)
+
+ override fun update(node: ContentNode) {
+ node.update(content, isElevationPossible, isInvisible)
+ }
+}
+
+private class ContentNode(
+ private var content: Content,
+ private var isElevationPossible: Boolean,
+ private var isInvisible: Boolean,
+) : DelegatingNode(), ApproachLayoutModifierNode {
+ private var containerDelegate = containerDelegate(isElevationPossible)
+
+ private fun containerDelegate(isElevationPossible: Boolean): ContainerNode? {
+ return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null
+ }
+
+ fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) {
+ if (content != this.content || isElevationPossible != this.isElevationPossible) {
+ this.content = content
+ this.isElevationPossible = isElevationPossible
+
+ containerDelegate?.let { undelegate(it) }
+ containerDelegate = containerDelegate(isElevationPossible)
+ }
+
+ this.isInvisible = isInvisible
+ }
+
+ override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean = false
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ check(isLookingAhead)
+ return measurable.measure(constraints).run {
+ content.targetSize = IntSize(width, height)
+ layout(width, height) {
+ if (!isInvisible) {
+ place(0, 0, zIndex = content.zIndex)
+ }
+ }
+ }
+ }
+
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ return measurable.measure(constraints).run {
+ layout(width, height) {
+ if (!isInvisible) {
+ place(0, 0, zIndex = content.zIndex)
+ }
+ }
+ }
+ }
+}
+
internal class ContentEffects(factory: OverscrollFactory) {
val overscrollEffect = factory.createOverscrollEffect()
val gestureEffect = GestureEffect(overscrollEffect)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
index 7f57798fb1b3..38acd4be80ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
@@ -35,6 +35,7 @@ internal class Scene(
zIndex: Float,
globalZIndex: Long,
effectFactory: OverscrollFactory,
+ val alwaysCompose: Boolean,
) : Content(key, layoutImpl, content, actions, zIndex, globalZIndex, effectFactory) {
override fun toString(): String {
return "Scene(key=$key)"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e23e234b1cad..312dd77fd53f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -22,6 +22,8 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
@@ -241,6 +243,15 @@ sealed interface TransitionState {
/** Additional gesture context whenever the transition is driven by a user gesture. */
abstract val gestureContext: GestureContext?
+ /**
+ * True when the transition reached the end and the progress won't be updated anymore.
+ *
+ * [isProgressStable] will be `true` before this [Transition] is completed while there are
+ * still custom transition animations settling.
+ */
+ var isProgressStable: Boolean by mutableStateOf(false)
+ private set
+
/** The CUJ covered by this transition. */
@CujType
val cuj: Int?
@@ -372,7 +383,11 @@ sealed interface TransitionState {
check(_coroutineScope == null) { "A Transition can be started only once." }
coroutineScope {
_coroutineScope = this
- run()
+ try {
+ run()
+ } finally {
+ isProgressStable = true
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt
new file mode 100644
index 000000000000..ac8a8c014af4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.mechanics
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.mutableFloatStateOf
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementStateScope
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import com.android.mechanics.MotionValue
+import com.android.mechanics.ProvidedGestureContext
+import com.android.mechanics.spec.InputDirection
+import com.android.mechanics.spec.MotionSpec
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Callback to create a [MotionSpec] on the first call to [CustomPropertyTransformation.transform]
+ */
+typealias SpecFactory =
+ PropertyTransformationScope.(content: ContentKey, element: ElementKey) -> MotionSpec
+
+/** Callback to compute the [MotionValue] per frame */
+typealias MotionValueInput =
+ PropertyTransformationScope.(progress: Float, content: ContentKey, element: ElementKey) -> Float
+
+/**
+ * Adapter to create a [MotionValue] and `keepRunning()` it temporarily while a
+ * [CustomPropertyTransformation] is in progress and until the animation settles.
+ *
+ * The [MotionValue]'s input is by default the transition progress.
+ */
+internal class TransitionScopedMechanicsAdapter(
+ private val computeInput: MotionValueInput = { progress, _, _ -> progress },
+ private val stableThreshold: Float = MotionValue.StableThresholdEffect,
+ private val label: String? = null,
+ private val createSpec: SpecFactory,
+) {
+
+ private val input = mutableFloatStateOf(0f)
+ private var motionValue: MotionValue? = null
+
+ fun PropertyTransformationScope.update(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): Float {
+ val progress = transition.progressTo(content)
+ input.floatValue = computeInput(progress, content, element)
+ var motionValue = motionValue
+
+ if (motionValue == null) {
+ motionValue =
+ MotionValue(
+ input::floatValue,
+ transition.gestureContext
+ ?: ProvidedGestureContext(
+ 0f,
+ appearDirection(content, element, transition),
+ ),
+ createSpec(content, element),
+ stableThreshold = stableThreshold,
+ label = label,
+ )
+ this@TransitionScopedMechanicsAdapter.motionValue = motionValue
+
+ transitionScope.launch {
+ motionValue.keepRunningWhile { !transition.isProgressStable || !isStable }
+ }
+ }
+
+ return motionValue.output
+ }
+
+ companion object {
+ /**
+ * Computes the InputDirection for a triggered transition of an element appearing /
+ * disappearing.
+ *
+ * Since [CustomPropertyTransformation] are only supported for non-shared elements, the
+ * [TransitionScopedMechanicsAdapter] is only used in the context of an element appearing /
+ * disappearing. This helper computes the direction to result in [InputDirection.Max] for an
+ * appear transition, and [InputDirection.Min] for a disappear transition.
+ */
+ @VisibleForTesting
+ internal fun ElementStateScope.appearDirection(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ ): InputDirection {
+ check(!transition.isInitiatedByUserInput)
+
+ val inMaxDirection =
+ when (transition) {
+ is TransitionState.Transition.ChangeScene -> {
+ val transitionTowardsContent = content == transition.toContent
+ val elementInContent = element.targetSize(content) != null
+ val isReversed = transition.currentScene != transition.toScene
+ (transitionTowardsContent xor elementInContent) xor !isReversed
+ }
+
+ is TransitionState.Transition.ShowOrHideOverlay -> {
+ val transitioningTowardsOverlay = transition.overlay == transition.toContent
+ val isReversed =
+ transitioningTowardsOverlay xor transition.isEffectivelyShown
+ transitioningTowardsOverlay xor isReversed
+ }
+
+ is TransitionState.Transition.ReplaceOverlay -> {
+ transition.effectivelyShownOverlay == content
+ }
+ }
+
+ return if (inMaxDirection) InputDirection.Max else InputDirection.Min
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index 2134510557d0..734de34dcd2f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -16,28 +16,20 @@
package com.android.compose.animation.scene.reveal
-import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.DeferredTargetAnimation
-import androidx.compose.animation.core.ExperimentalAnimatableApi
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
-import androidx.compose.ui.unit.Dp
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastCoerceAtLeast
-import androidx.compose.ui.util.fastCoerceAtMost
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.OverlayKey
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.mechanics.MotionValueInput
+import com.android.compose.animation.scene.mechanics.TransitionScopedMechanicsAdapter
import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.PropertyTransformationScope
-import kotlin.math.roundToInt
+import com.android.mechanics.MotionValue
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
import kotlinx.coroutines.CoroutineScope
interface ContainerRevealHaptics {
@@ -53,9 +45,15 @@ interface ContainerRevealHaptics {
fun onRevealThresholdCrossed(revealed: Boolean)
}
-/** Animate the reveal of [container] by animating its size. */
+/**
+ * Animate the reveal of [container] by animating its size.
+ *
+ * This implicitly sets the [distance] of the transition to the target size of [container]
+ */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun TransitionBuilder.verticalContainerReveal(
container: ElementKey,
+ motionSpec: VerticalExpandContainerSpec,
haptics: ContainerRevealHaptics,
) {
// Make the swipe distance be exactly the target height of the container.
@@ -76,188 +74,73 @@ fun TransitionBuilder.verticalContainerReveal(
(targetSizeInToContent?.height ?: targetSizeInFromContent?.height)?.toFloat() ?: 0f
}
- // TODO(b/376438969): Improve the motion of this gesture using Motion Mechanics.
-
- // The min distance to swipe before triggering the reveal spring.
- val distanceThreshold = 80.dp
-
- // The minimum height of the container.
- val minHeight = 10.dp
-
- // The amount removed from the container width at 0% progress.
- val widthDelta = 140.dp
-
- // The ratio at which the distance is tracked before reaching the threshold, e.g. if the user
- // drags 60dp then the height will be 60dp * 0.25f = 15dp.
- val trackingRatio = 0.25f
-
- // The max progress starting from which the container should always be visible, even if we are
- // animating the container out. This is used so that we don't immediately fade out the container
- // when triggering a one-off animation that hides it.
- val alphaProgressThreshold = 0.05f
-
- // The spring animating the size of the container.
- val sizeSpec = spring<Float>(stiffness = 380f, dampingRatio = 0.9f)
-
- // The spring animating the alpha of the container.
- val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
-
- // Size transformation.
- transformation(container) {
- VerticalContainerRevealSizeTransformation(
- haptics,
- distanceThreshold,
- trackingRatio,
- minHeight,
- widthDelta,
- sizeSpec,
- )
- }
-
- // Alpha transformation.
- transformation(container) {
- ContainerRevealAlphaTransformation(alphaSpec, alphaProgressThreshold)
- }
-}
-
-@OptIn(ExperimentalAnimatableApi::class)
-private class VerticalContainerRevealSizeTransformation(
- private val haptics: ContainerRevealHaptics,
- private val distanceThreshold: Dp,
- private val trackingRatio: Float,
- private val minHeight: Dp,
- private val widthDelta: Dp,
- private val spec: FiniteAnimationSpec<Float>,
-) : CustomPropertyTransformation<IntSize> {
- override val property = PropertyTransformation.Property.Size
-
- private val widthAnimation = DeferredTargetAnimation(Float.VectorConverter)
- private val heightAnimation = DeferredTargetAnimation(Float.VectorConverter)
-
- private var previousHasReachedThreshold: Boolean? = null
-
- override fun PropertyTransformationScope.transform(
- content: ContentKey,
- element: ElementKey,
- transition: TransitionState.Transition,
- transitionScope: CoroutineScope,
- ): IntSize {
- // The distance to go to 100%. Note that we don't use
- // TransitionState.HasOverscrollProperties.absoluteDistance because the transition will not
- // implement HasOverscrollProperties if the transition is triggered and not gesture based.
+ // TODO(b/392534646) Add haptics back
+ val heightInput: MotionValueInput = { progress, content, element ->
val idleSize = checkNotNull(element.targetSize(content))
- val userActionDistance = idleSize.height
- val progress = transition.progressTo(content)
- val distance = (progress * userActionDistance).fastCoerceAtLeast(0f)
- val threshold = distanceThreshold.toPx()
-
- // Width.
- val widthDelta = widthDelta.toPx()
- val width =
- (idleSize.width - widthDelta +
- animateSize(
- size = widthDelta,
- distance = distance,
- threshold = threshold,
- transitionScope = transitionScope,
- animation = widthAnimation,
- ))
- .roundToInt()
-
- // Height.
- val minHeight = minHeight.toPx()
- val height =
- (
- // 1) The minimum size of the container.
- minHeight +
-
- // 2) The animated size between the minimum size and the threshold.
- animateSize(
- size = threshold - minHeight,
- distance = distance,
- threshold = threshold,
- transitionScope = transitionScope,
- animation = heightAnimation,
- ) +
-
- // 3) The remaining height after the threshold, tracking the finger.
- (distance - threshold).fastCoerceAtLeast(0f))
- .roundToInt()
- .fastCoerceAtMost(idleSize.height)
-
- // Haptics.
- val hasReachedThreshold = distance >= threshold
- if (
- previousHasReachedThreshold != null &&
- hasReachedThreshold != previousHasReachedThreshold &&
- transition.isUserInputOngoing
- ) {
- haptics.onRevealThresholdCrossed(revealed = hasReachedThreshold)
- }
- previousHasReachedThreshold = hasReachedThreshold
-
- return IntSize(width = width, height = height)
+ val targetHeight = idleSize.height.toFloat()
+ targetHeight * progress
}
- /**
- * Animate a size up to [size], so that it is equal to 0f when distance is 0f and equal to
- * [size] when `distance >= threshold`, taking the [trackingRatio] into account.
- */
- @OptIn(ExperimentalAnimatableApi::class)
- private fun animateSize(
- size: Float,
- distance: Float,
- threshold: Float,
- transitionScope: CoroutineScope,
- animation: DeferredTargetAnimation<Float, AnimationVector1D>,
- ): Float {
- val trackingSize = distance.fastCoerceAtMost(threshold) / threshold * size * trackingRatio
- val springTarget =
- if (distance >= threshold) {
- size * (1f - trackingRatio)
- } else {
- 0f
+ transformation(container) {
+ object : CustomPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
+ val heightValue =
+ TransitionScopedMechanicsAdapter(
+ computeInput = heightInput,
+ stableThreshold = MotionValue.StableThresholdSpatial,
+ label = "verticalContainerReveal::height",
+ ) { _, _ ->
+ motionSpec.createHeightSpec(motionScheme, density = this)
+ }
+ val widthValue =
+ TransitionScopedMechanicsAdapter(
+ computeInput = heightInput,
+ stableThreshold = MotionValue.StableThresholdSpatial,
+ label = "verticalContainerReveal::width",
+ ) { content, element ->
+ val idleSize = checkNotNull(element.targetSize(content))
+ val intrinsicWidth = idleSize.width.toFloat()
+ motionSpec.createWidthSpec(intrinsicWidth, motionScheme, density = this)
+ }
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): IntSize {
+
+ val height =
+ with(heightValue) { update(content, element, transition, transitionScope) }
+ val width =
+ with(widthValue) { update(content, element, transition, transitionScope) }
+
+ return IntSize(width.toInt(), height.toInt())
}
- val springSize = animation.updateTarget(springTarget, transitionScope, spec)
- return trackingSize + springSize
- }
-}
-
-@OptIn(ExperimentalAnimatableApi::class)
-private class ContainerRevealAlphaTransformation(
- private val spec: FiniteAnimationSpec<Float>,
- private val progressThreshold: Float,
-) : CustomPropertyTransformation<Float> {
- override val property = PropertyTransformation.Property.Alpha
- private val alphaAnimation = DeferredTargetAnimation(Float.VectorConverter)
-
- override fun PropertyTransformationScope.transform(
- content: ContentKey,
- element: ElementKey,
- transition: TransitionState.Transition,
- transitionScope: CoroutineScope,
- ): Float {
- return alphaAnimation.updateTarget(targetAlpha(transition, content), transitionScope, spec)
- }
-
- private fun targetAlpha(transition: TransitionState.Transition, content: ContentKey): Float {
- if (transition.isUserInputOngoing) {
- return if (transition.progressTo(content) > 0f) 1f else 0f
}
+ }
- // The transition was committed (the user released their finger), so the alpha depends on
- // whether we are animating towards the content (showing the container) or away from it
- // (hiding the container).
- val isShowingContainer =
- when (content) {
- is SceneKey -> transition.currentScene == content
- is OverlayKey -> transition.currentOverlays.contains(content)
+ transformation(container) {
+ object : CustomPropertyTransformation<Float> {
+
+ override val property = PropertyTransformation.Property.Alpha
+ val alphaValue =
+ TransitionScopedMechanicsAdapter(
+ computeInput = heightInput,
+ label = "verticalContainerReveal::alpha",
+ ) { _, _ ->
+ motionSpec.createAlphaSpec(motionScheme, density = this)
+ }
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): Float {
+ return with(alphaValue) { update(content, element, transition, transitionScope) }
}
-
- return if (isShowingContainer || transition.progressTo(content) >= progressThreshold) {
- 1f
- } else {
- 0f
}
}
}
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
index 2ab27af37700..d63450b27390 100644
--- a/packages/SystemUI/compose/scene/tests/Android.bp
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -42,6 +42,7 @@ android_test {
"PlatformMotionTestingCompose",
"androidx.test.runner",
"androidx.test.ext.junit",
+ "platform-parametric-runner-lib",
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json
new file mode 100644
index 000000000000..57f67665242c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json
@@ -0,0 +1,654 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816,
+ 832,
+ 848,
+ 864,
+ 880,
+ 896,
+ 912,
+ 928,
+ 944,
+ 960,
+ 976,
+ 992
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50.4
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 52.4,
+ "y": 50
+ },
+ {
+ "x": 56,
+ "y": 50
+ },
+ {
+ "x": 58.8,
+ "y": 50
+ },
+ {
+ "x": 60.8,
+ "y": 50
+ },
+ {
+ "x": 62,
+ "y": 50
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 63.6,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 393.2
+ },
+ {
+ "width": 188,
+ "height": 393.2
+ },
+ {
+ "width": 188,
+ "height": 386
+ },
+ {
+ "width": 188,
+ "height": 379.6
+ },
+ {
+ "width": 188,
+ "height": 372.8
+ },
+ {
+ "width": 188,
+ "height": 366.8
+ },
+ {
+ "width": 188,
+ "height": 360.4
+ },
+ {
+ "width": 188,
+ "height": 354
+ },
+ {
+ "width": 188,
+ "height": 347.6
+ },
+ {
+ "width": 188,
+ "height": 341.2
+ },
+ {
+ "width": 188,
+ "height": 341.2
+ },
+ {
+ "width": 188,
+ "height": 334
+ },
+ {
+ "width": 188,
+ "height": 328
+ },
+ {
+ "width": 188,
+ "height": 321.6
+ },
+ {
+ "width": 188,
+ "height": 315.2
+ },
+ {
+ "width": 188,
+ "height": 308.8
+ },
+ {
+ "width": 188,
+ "height": 302.4
+ },
+ {
+ "width": 188,
+ "height": 296
+ },
+ {
+ "width": 188,
+ "height": 289.6
+ },
+ {
+ "width": 188,
+ "height": 289.6
+ },
+ {
+ "width": 188,
+ "height": 282.8
+ },
+ {
+ "width": 188,
+ "height": 276.4
+ },
+ {
+ "width": 188,
+ "height": 270
+ },
+ {
+ "width": 188,
+ "height": 263.6
+ },
+ {
+ "width": 188,
+ "height": 257.2
+ },
+ {
+ "width": 188,
+ "height": 250.8
+ },
+ {
+ "width": 188,
+ "height": 244.4
+ },
+ {
+ "width": 188,
+ "height": 238
+ },
+ {
+ "width": 188,
+ "height": 238
+ },
+ {
+ "width": 188,
+ "height": 231.2
+ },
+ {
+ "width": 188,
+ "height": 224.8
+ },
+ {
+ "width": 188,
+ "height": 218.4
+ },
+ {
+ "width": 188,
+ "height": 212
+ },
+ {
+ "width": 188,
+ "height": 212
+ },
+ {
+ "width": 188,
+ "height": 192.4
+ },
+ {
+ "width": 188,
+ "height": 159.6
+ },
+ {
+ "width": 188,
+ "height": 124.4
+ },
+ {
+ "width": 188,
+ "height": 92.8
+ },
+ {
+ "width": 183.2,
+ "height": 66.4
+ },
+ {
+ "width": 176,
+ "height": 46
+ },
+ {
+ "width": 170.4,
+ "height": 28.8
+ },
+ {
+ "width": 166.8,
+ "height": 15.2
+ },
+ {
+ "width": 164,
+ "height": 6.4
+ },
+ {
+ "width": 162.4,
+ "height": 0.8
+ },
+ {
+ "width": 161.2,
+ "height": 0
+ },
+ {
+ "width": 160.4,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9808927,
+ 0.8211168,
+ 0.61845565,
+ 0.43834114,
+ 0.29850912,
+ 0.19755232,
+ 0.12793064,
+ 0.08142871,
+ 0.051099956,
+ 0.031684637,
+ 0.019442618,
+ 0.011821032,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json
new file mode 100644
index 000000000000..01bc852cf7f4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json
@@ -0,0 +1,644 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816,
+ 832,
+ 848,
+ 864,
+ 880,
+ 896,
+ 912,
+ 928,
+ 944,
+ 960,
+ 976
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50.8,
+ "y": 52
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 52.4,
+ "y": 50
+ },
+ {
+ "x": 55.6,
+ "y": 50
+ },
+ {
+ "x": 58.4,
+ "y": 50
+ },
+ {
+ "x": 60.4,
+ "y": 50
+ },
+ {
+ "x": 61.6,
+ "y": 50
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 63.2,
+ "y": 50
+ },
+ {
+ "x": 63.6,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 390
+ },
+ {
+ "width": 188,
+ "height": 390
+ },
+ {
+ "width": 188,
+ "height": 379.2
+ },
+ {
+ "width": 188,
+ "height": 371.2
+ },
+ {
+ "width": 188,
+ "height": 363.2
+ },
+ {
+ "width": 188,
+ "height": 355.2
+ },
+ {
+ "width": 188,
+ "height": 347.2
+ },
+ {
+ "width": 188,
+ "height": 339.2
+ },
+ {
+ "width": 188,
+ "height": 331.2
+ },
+ {
+ "width": 188,
+ "height": 323.2
+ },
+ {
+ "width": 188,
+ "height": 323.2
+ },
+ {
+ "width": 188,
+ "height": 314.8
+ },
+ {
+ "width": 188,
+ "height": 306.8
+ },
+ {
+ "width": 188,
+ "height": 298.8
+ },
+ {
+ "width": 188,
+ "height": 290.8
+ },
+ {
+ "width": 188,
+ "height": 282.8
+ },
+ {
+ "width": 188,
+ "height": 274.8
+ },
+ {
+ "width": 188,
+ "height": 266.8
+ },
+ {
+ "width": 188,
+ "height": 258.8
+ },
+ {
+ "width": 188,
+ "height": 258.8
+ },
+ {
+ "width": 188,
+ "height": 250.4
+ },
+ {
+ "width": 188,
+ "height": 242.4
+ },
+ {
+ "width": 188,
+ "height": 234.4
+ },
+ {
+ "width": 188,
+ "height": 226.4
+ },
+ {
+ "width": 188,
+ "height": 218.4
+ },
+ {
+ "width": 188,
+ "height": 210.4
+ },
+ {
+ "width": 188,
+ "height": 202.4
+ },
+ {
+ "width": 188,
+ "height": 194.4
+ },
+ {
+ "width": 188,
+ "height": 194.4
+ },
+ {
+ "width": 188,
+ "height": 185.6
+ },
+ {
+ "width": 188,
+ "height": 178
+ },
+ {
+ "width": 188,
+ "height": 170
+ },
+ {
+ "width": 188,
+ "height": 161.6
+ },
+ {
+ "width": 188,
+ "height": 161.6
+ },
+ {
+ "width": 188,
+ "height": 144.8
+ },
+ {
+ "width": 188,
+ "height": 118.8
+ },
+ {
+ "width": 188,
+ "height": 92
+ },
+ {
+ "width": 183.6,
+ "height": 68
+ },
+ {
+ "width": 176.8,
+ "height": 48.4
+ },
+ {
+ "width": 171.6,
+ "height": 32
+ },
+ {
+ "width": 167.6,
+ "height": 18
+ },
+ {
+ "width": 164.8,
+ "height": 8.8
+ },
+ {
+ "width": 162.8,
+ "height": 2.8
+ },
+ {
+ "width": 161.6,
+ "height": 0
+ },
+ {
+ "width": 160.8,
+ "height": 0
+ },
+ {
+ "width": 160.4,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9967737,
+ 0.86538374,
+ 0.66414475,
+ 0.47619528,
+ 0.32686388,
+ 0.21757984,
+ 0.14153665,
+ 0.09041709,
+ 0.05691254,
+ 0.035380244,
+ 0.02175957,
+ 0.01325649,
+ 0.008007765,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json
new file mode 100644
index 000000000000..b6e423afc6c4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragOpen.json
@@ -0,0 +1,544 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 61.6,
+ "y": 50
+ },
+ {
+ "x": 60.8,
+ "y": 50
+ },
+ {
+ "x": 59.6,
+ "y": 50
+ },
+ {
+ "x": 58.4,
+ "y": 50
+ },
+ {
+ "x": 57.2,
+ "y": 50
+ },
+ {
+ "x": 56,
+ "y": 50
+ },
+ {
+ "x": 55.2,
+ "y": 50
+ },
+ {
+ "x": 54,
+ "y": 50
+ },
+ {
+ "x": 54,
+ "y": 50
+ },
+ {
+ "x": 52.8,
+ "y": 50
+ },
+ {
+ "x": 51.6,
+ "y": 50
+ },
+ {
+ "x": 50.4,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 162.4,
+ "height": 1.6
+ },
+ {
+ "width": 162.4,
+ "height": 1.6
+ },
+ {
+ "width": 164.8,
+ "height": 3.2
+ },
+ {
+ "width": 166.8,
+ "height": 4.8
+ },
+ {
+ "width": 169.2,
+ "height": 6.4
+ },
+ {
+ "width": 171.6,
+ "height": 8
+ },
+ {
+ "width": 173.6,
+ "height": 9.6
+ },
+ {
+ "width": 176,
+ "height": 11.2
+ },
+ {
+ "width": 178,
+ "height": 12.8
+ },
+ {
+ "width": 180.4,
+ "height": 14.4
+ },
+ {
+ "width": 180.4,
+ "height": 14.4
+ },
+ {
+ "width": 182.8,
+ "height": 16.4
+ },
+ {
+ "width": 185.2,
+ "height": 18
+ },
+ {
+ "width": 187.2,
+ "height": 19.6
+ },
+ {
+ "width": 188,
+ "height": 25.6
+ },
+ {
+ "width": 188,
+ "height": 36.4
+ },
+ {
+ "width": 188,
+ "height": 45.6
+ },
+ {
+ "width": 188,
+ "height": 59.2
+ },
+ {
+ "width": 188,
+ "height": 72.8
+ },
+ {
+ "width": 188,
+ "height": 79.6
+ },
+ {
+ "width": 188,
+ "height": 92.8
+ },
+ {
+ "width": 188,
+ "height": 104.4
+ },
+ {
+ "width": 188,
+ "height": 115.2
+ },
+ {
+ "width": 188,
+ "height": 125.2
+ },
+ {
+ "width": 188,
+ "height": 134.8
+ },
+ {
+ "width": 188,
+ "height": 143.2
+ },
+ {
+ "width": 188,
+ "height": 151.2
+ },
+ {
+ "width": 188,
+ "height": 158.8
+ },
+ {
+ "width": 188,
+ "height": 160
+ },
+ {
+ "width": 188,
+ "height": 167.2
+ },
+ {
+ "width": 188,
+ "height": 174.4
+ },
+ {
+ "width": 188,
+ "height": 180.8
+ },
+ {
+ "width": 188,
+ "height": 187.6
+ },
+ {
+ "width": 188,
+ "height": 188
+ },
+ {
+ "width": 188,
+ "height": 207.2
+ },
+ {
+ "width": 188,
+ "height": 240
+ },
+ {
+ "width": 188,
+ "height": 275.2
+ },
+ {
+ "width": 188,
+ "height": 306.8
+ },
+ {
+ "width": 188,
+ "height": 333.2
+ },
+ {
+ "width": 188,
+ "height": 353.6
+ },
+ {
+ "width": 188,
+ "height": 368.8
+ },
+ {
+ "width": 188,
+ "height": 380
+ },
+ {
+ "width": 188,
+ "height": 387.6
+ },
+ {
+ "width": 188,
+ "height": 392.4
+ },
+ {
+ "width": 188,
+ "height": 395.6
+ },
+ {
+ "width": 188,
+ "height": 398
+ },
+ {
+ "width": 188,
+ "height": 398.8
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ },
+ {
+ "width": 188,
+ "height": 400
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.0067873597,
+ 0.0612576,
+ 0.19080025,
+ 0.39327443,
+ 0.5711931,
+ 0.70855826,
+ 0.8074064,
+ 0.8754226,
+ 0.9207788,
+ 0.95032376,
+ 0.9692185,
+ 0.98112255,
+ 0.9885286,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json
new file mode 100644
index 000000000000..a82db346ed58
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json
@@ -0,0 +1,444 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50.4,
+ "y": 50.8
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 51.2,
+ "y": 50
+ },
+ {
+ "x": 55.6,
+ "y": 50
+ },
+ {
+ "x": 58.8,
+ "y": 50
+ },
+ {
+ "x": 60.8,
+ "y": 50
+ },
+ {
+ "x": 62,
+ "y": 50
+ },
+ {
+ "x": 63.2,
+ "y": 50
+ },
+ {
+ "x": 63.6,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 389.6
+ },
+ {
+ "width": 188,
+ "height": 378.8
+ },
+ {
+ "width": 188,
+ "height": 366
+ },
+ {
+ "width": 188,
+ "height": 352
+ },
+ {
+ "width": 188,
+ "height": 352
+ },
+ {
+ "width": 188,
+ "height": 316.8
+ },
+ {
+ "width": 188,
+ "height": 261.2
+ },
+ {
+ "width": 188,
+ "height": 202.8
+ },
+ {
+ "width": 188,
+ "height": 150.8
+ },
+ {
+ "width": 188,
+ "height": 107.6
+ },
+ {
+ "width": 186,
+ "height": 74.4
+ },
+ {
+ "width": 177.2,
+ "height": 49.6
+ },
+ {
+ "width": 170.8,
+ "height": 29.6
+ },
+ {
+ "width": 166.8,
+ "height": 12.8
+ },
+ {
+ "width": 164,
+ "height": 2.4
+ },
+ {
+ "width": 162,
+ "height": 0
+ },
+ {
+ "width": 160.8,
+ "height": 0
+ },
+ {
+ "width": 160.4,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9833227,
+ 0.8263634,
+ 0.623688,
+ 0.44261706,
+ 0.3016883,
+ 0.1997872,
+ 0.12944388,
+ 0.08242595,
+ 0.051743627,
+ 0.032093227,
+ 0.019698441,
+ 0.0119793415,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json
new file mode 100644
index 000000000000..6dc5a0e79e81
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingOpen.json
@@ -0,0 +1,354 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 62.4,
+ "y": 50
+ },
+ {
+ "x": 61.2,
+ "y": 50
+ },
+ {
+ "x": 59.2,
+ "y": 50
+ },
+ {
+ "x": 57.2,
+ "y": 50
+ },
+ {
+ "x": 54.8,
+ "y": 50
+ },
+ {
+ "x": 52.4,
+ "y": 50
+ },
+ {
+ "x": 52.4,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 163.2,
+ "height": 2
+ },
+ {
+ "width": 166,
+ "height": 4.4
+ },
+ {
+ "width": 170,
+ "height": 6.8
+ },
+ {
+ "width": 174,
+ "height": 10
+ },
+ {
+ "width": 178.4,
+ "height": 13.2
+ },
+ {
+ "width": 183.6,
+ "height": 16.8
+ },
+ {
+ "width": 183.6,
+ "height": 16.8
+ },
+ {
+ "width": 188,
+ "height": 44.4
+ },
+ {
+ "width": 188,
+ "height": 103.6
+ },
+ {
+ "width": 188,
+ "height": 166
+ },
+ {
+ "width": 188,
+ "height": 222.4
+ },
+ {
+ "width": 188,
+ "height": 270
+ },
+ {
+ "width": 188,
+ "height": 307.2
+ },
+ {
+ "width": 188,
+ "height": 335.6
+ },
+ {
+ "width": 188,
+ "height": 356.4
+ },
+ {
+ "width": 188,
+ "height": 371.2
+ },
+ {
+ "width": 188,
+ "height": 381.6
+ },
+ {
+ "width": 188,
+ "height": 388.8
+ },
+ {
+ "width": 188,
+ "height": 393.2
+ },
+ {
+ "width": 188,
+ "height": 396
+ },
+ {
+ "width": 188,
+ "height": 398
+ },
+ {
+ "width": 188,
+ "height": 398.8
+ },
+ {
+ "width": 188,
+ "height": 399.2
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0,
+ 0.008216977,
+ 0.06259775,
+ 0.19032806,
+ 0.39281356,
+ 0.57081985,
+ 0.7082821,
+ 0.80721295,
+ 0.8752918,
+ 0.9206928,
+ 0.95026827,
+ 0.9691833,
+ 0.98110056,
+ 0.988515,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json
new file mode 100644
index 000000000000..1cd971aa2898
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_magneticDetachAndReattach.json
@@ -0,0 +1,724 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816,
+ 832,
+ 848,
+ 864,
+ 880,
+ 896,
+ 912,
+ 928,
+ 944,
+ 960,
+ 976,
+ 992,
+ 1008,
+ 1024,
+ 1040,
+ 1056,
+ 1072,
+ 1088,
+ 1104
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 62,
+ "y": 50
+ },
+ {
+ "x": 61.2,
+ "y": 50
+ },
+ {
+ "x": 60.4,
+ "y": 50
+ },
+ {
+ "x": 59.6,
+ "y": 50
+ },
+ {
+ "x": 58.8,
+ "y": 50
+ },
+ {
+ "x": 58,
+ "y": 50
+ },
+ {
+ "x": 57.2,
+ "y": 50
+ },
+ {
+ "x": 56.4,
+ "y": 50
+ },
+ {
+ "x": 55.6,
+ "y": 50
+ },
+ {
+ "x": 55.2,
+ "y": 50
+ },
+ {
+ "x": 54.4,
+ "y": 50
+ },
+ {
+ "x": 53.6,
+ "y": 50
+ },
+ {
+ "x": 53.2,
+ "y": 50
+ },
+ {
+ "x": 52.8,
+ "y": 50
+ },
+ {
+ "x": 52,
+ "y": 50
+ },
+ {
+ "x": 51.6,
+ "y": 50
+ },
+ {
+ "x": 51.2,
+ "y": 50
+ },
+ {
+ "x": 50.8,
+ "y": 50
+ },
+ {
+ "x": 50.4,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50.4,
+ "y": 50
+ },
+ {
+ "x": 50.8,
+ "y": 50
+ },
+ {
+ "x": 51.2,
+ "y": 50
+ },
+ {
+ "x": 51.6,
+ "y": 50
+ },
+ {
+ "x": 52,
+ "y": 50
+ },
+ {
+ "x": 52.8,
+ "y": 50
+ },
+ {
+ "x": 53.2,
+ "y": 50
+ },
+ {
+ "x": 53.6,
+ "y": 50
+ },
+ {
+ "x": 54.4,
+ "y": 50
+ },
+ {
+ "x": 55.2,
+ "y": 50
+ },
+ {
+ "x": 55.6,
+ "y": 50
+ },
+ {
+ "x": 56.4,
+ "y": 50
+ },
+ {
+ "x": 57.2,
+ "y": 50
+ },
+ {
+ "x": 58,
+ "y": 50
+ },
+ {
+ "x": 58.8,
+ "y": 50
+ },
+ {
+ "x": 59.6,
+ "y": 50
+ },
+ {
+ "x": 60.4,
+ "y": 50
+ },
+ {
+ "x": 61.2,
+ "y": 50
+ },
+ {
+ "x": 62,
+ "y": 50
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 63.6,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 162.4,
+ "height": 1.6
+ },
+ {
+ "width": 164,
+ "height": 2.8
+ },
+ {
+ "width": 166,
+ "height": 4
+ },
+ {
+ "width": 167.6,
+ "height": 5.2
+ },
+ {
+ "width": 169.2,
+ "height": 6.4
+ },
+ {
+ "width": 170.8,
+ "height": 7.6
+ },
+ {
+ "width": 172.4,
+ "height": 8.8
+ },
+ {
+ "width": 174,
+ "height": 10
+ },
+ {
+ "width": 175.2,
+ "height": 10.8
+ },
+ {
+ "width": 176.8,
+ "height": 12
+ },
+ {
+ "width": 178,
+ "height": 12.8
+ },
+ {
+ "width": 179.2,
+ "height": 13.6
+ },
+ {
+ "width": 180.8,
+ "height": 14.8
+ },
+ {
+ "width": 182,
+ "height": 15.6
+ },
+ {
+ "width": 182.8,
+ "height": 16.4
+ },
+ {
+ "width": 184,
+ "height": 17.2
+ },
+ {
+ "width": 184.8,
+ "height": 17.6
+ },
+ {
+ "width": 186,
+ "height": 18.4
+ },
+ {
+ "width": 186.8,
+ "height": 19.2
+ },
+ {
+ "width": 187.6,
+ "height": 19.6
+ },
+ {
+ "width": 188,
+ "height": 21.2
+ },
+ {
+ "width": 188,
+ "height": 24.8
+ },
+ {
+ "width": 188,
+ "height": 30
+ },
+ {
+ "width": 188,
+ "height": 38
+ },
+ {
+ "width": 188,
+ "height": 46
+ },
+ {
+ "width": 188,
+ "height": 54
+ },
+ {
+ "width": 188,
+ "height": 61.2
+ },
+ {
+ "width": 188,
+ "height": 66.8
+ },
+ {
+ "width": 188,
+ "height": 71.6
+ },
+ {
+ "width": 188,
+ "height": 75.6
+ },
+ {
+ "width": 188,
+ "height": 78
+ },
+ {
+ "width": 188,
+ "height": 79.6
+ },
+ {
+ "width": 188,
+ "height": 80.8
+ },
+ {
+ "width": 188,
+ "height": 80.8
+ },
+ {
+ "width": 188,
+ "height": 80.4
+ },
+ {
+ "width": 188,
+ "height": 79.6
+ },
+ {
+ "width": 187.6,
+ "height": 78
+ },
+ {
+ "width": 186.8,
+ "height": 76.4
+ },
+ {
+ "width": 186,
+ "height": 74
+ },
+ {
+ "width": 184.8,
+ "height": 71.6
+ },
+ {
+ "width": 184,
+ "height": 69.2
+ },
+ {
+ "width": 182.8,
+ "height": 66
+ },
+ {
+ "width": 182,
+ "height": 62.8
+ },
+ {
+ "width": 180.8,
+ "height": 59.2
+ },
+ {
+ "width": 179.2,
+ "height": 55.6
+ },
+ {
+ "width": 178,
+ "height": 52
+ },
+ {
+ "width": 176.8,
+ "height": 48
+ },
+ {
+ "width": 175.2,
+ "height": 44
+ },
+ {
+ "width": 174,
+ "height": 40
+ },
+ {
+ "width": 172.4,
+ "height": 37.6
+ },
+ {
+ "width": 170.8,
+ "height": 38
+ },
+ {
+ "width": 169.2,
+ "height": 30.4
+ },
+ {
+ "width": 167.6,
+ "height": 25.2
+ },
+ {
+ "width": 166,
+ "height": 20.4
+ },
+ {
+ "width": 164,
+ "height": 16
+ },
+ {
+ "width": 162.4,
+ "height": 12.4
+ },
+ {
+ "width": 160.8,
+ "height": 9.2
+ },
+ {
+ "width": 160,
+ "height": 6.8
+ },
+ {
+ "width": 160,
+ "height": 5.2
+ },
+ {
+ "width": 160,
+ "height": 3.6
+ },
+ {
+ "width": 160,
+ "height": 2.4
+ },
+ {
+ "width": 160,
+ "height": 1.6
+ },
+ {
+ "width": 160,
+ "height": 0.8
+ },
+ {
+ "width": 160,
+ "height": 0.4
+ },
+ {
+ "width": 160,
+ "height": 0.4
+ },
+ {
+ "width": 160,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.012518823,
+ 0.0741024,
+ 0.2254293,
+ 0.42628878,
+ 0.5976641,
+ 0.7280312,
+ 0.82100236,
+ 0.8845844,
+ 0.9267946,
+ 0.95419544,
+ 0.9716705,
+ 0.98265487,
+ 0.98947525,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9944124,
+ 0.9417388,
+ 0.8184184,
+ 0.6157812,
+ 0.4361611,
+ 0.2968906,
+ 0.19641554,
+ 0.12716137,
+ 0.080921985,
+ 0.050773025,
+ 0.03147719,
+ 0.019312752,
+ 0.011740655,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json
new file mode 100644
index 000000000000..1030455e873f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealCloseTransition.json
@@ -0,0 +1,324 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 53.2,
+ "y": 50
+ },
+ {
+ "x": 57.2,
+ "y": 50
+ },
+ {
+ "x": 59.6,
+ "y": 50
+ },
+ {
+ "x": 61.6,
+ "y": 50
+ },
+ {
+ "x": 62.8,
+ "y": 50
+ },
+ {
+ "x": 63.6,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 64,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 372
+ },
+ {
+ "width": 188,
+ "height": 312.8
+ },
+ {
+ "width": 188,
+ "height": 246.8
+ },
+ {
+ "width": 188,
+ "height": 185.2
+ },
+ {
+ "width": 188,
+ "height": 133.6
+ },
+ {
+ "width": 188,
+ "height": 93.2
+ },
+ {
+ "width": 181.6,
+ "height": 62.8
+ },
+ {
+ "width": 174,
+ "height": 40.8
+ },
+ {
+ "width": 168.8,
+ "height": 22.4
+ },
+ {
+ "width": 165.2,
+ "height": 10
+ },
+ {
+ "width": 162.8,
+ "height": 2.4
+ },
+ {
+ "width": 161.2,
+ "height": 0
+ },
+ {
+ "width": 160.4,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 160,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.91758585,
+ 0.72435355,
+ 0.52812576,
+ 0.3665868,
+ 0.24600428,
+ 0.16102076,
+ 0.103373945,
+ 0.06533456,
+ 0.04075712,
+ 0.025142312,
+ 0.015358448,
+ 0.0092999935,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json
new file mode 100644
index 000000000000..622c29eebfb4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_triggeredRevealOpenTransition.json
@@ -0,0 +1,244 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 64,
+ "y": 50
+ },
+ {
+ "x": 59.2,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 160,
+ "height": 0
+ },
+ {
+ "width": 169.6,
+ "height": 6.8
+ },
+ {
+ "width": 188,
+ "height": 26.8
+ },
+ {
+ "width": 188,
+ "height": 95.6
+ },
+ {
+ "width": 188,
+ "height": 163.2
+ },
+ {
+ "width": 188,
+ "height": 222
+ },
+ {
+ "width": 188,
+ "height": 269.6
+ },
+ {
+ "width": 188,
+ "height": 307.2
+ },
+ {
+ "width": 188,
+ "height": 335.2
+ },
+ {
+ "width": 188,
+ "height": 356
+ },
+ {
+ "width": 188,
+ "height": 370.4
+ },
+ {
+ "width": 188,
+ "height": 380.8
+ },
+ {
+ "width": 188,
+ "height": 387.6
+ },
+ {
+ "width": 188,
+ "height": 392.4
+ },
+ {
+ "width": 188,
+ "height": 395.2
+ },
+ {
+ "width": 188,
+ "height": 397.2
+ },
+ {
+ "width": 188,
+ "height": 398
+ },
+ {
+ "width": 188,
+ "height": 398.8
+ },
+ {
+ "width": 188,
+ "height": 399.2
+ },
+ {
+ "width": 188,
+ "height": 399.2
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0.05698657,
+ 0.24197984,
+ 0.44158113,
+ 0.6097554,
+ 0.73685503,
+ 0.8271309,
+ 0.8886989,
+ 0.9294886,
+ 0.9559254,
+ 0.97276413,
+ 0.98333716,
+ 0.98989624,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json
new file mode 100644
index 000000000000..59e8b51412b8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json
@@ -0,0 +1,634 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816,
+ 832,
+ 848,
+ 864,
+ 880,
+ 896,
+ 912,
+ 928,
+ 944,
+ 960
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50.4
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 393.2
+ },
+ {
+ "width": 188,
+ "height": 393.2
+ },
+ {
+ "width": 188,
+ "height": 386
+ },
+ {
+ "width": 188,
+ "height": 379.6
+ },
+ {
+ "width": 188,
+ "height": 372.8
+ },
+ {
+ "width": 188,
+ "height": 366.8
+ },
+ {
+ "width": 188,
+ "height": 360.4
+ },
+ {
+ "width": 188,
+ "height": 354
+ },
+ {
+ "width": 188,
+ "height": 347.6
+ },
+ {
+ "width": 188,
+ "height": 341.2
+ },
+ {
+ "width": 188,
+ "height": 341.2
+ },
+ {
+ "width": 188,
+ "height": 334
+ },
+ {
+ "width": 188,
+ "height": 328
+ },
+ {
+ "width": 188,
+ "height": 321.6
+ },
+ {
+ "width": 188,
+ "height": 315.2
+ },
+ {
+ "width": 188,
+ "height": 308.8
+ },
+ {
+ "width": 188,
+ "height": 302.4
+ },
+ {
+ "width": 188,
+ "height": 296
+ },
+ {
+ "width": 188,
+ "height": 289.6
+ },
+ {
+ "width": 188,
+ "height": 289.6
+ },
+ {
+ "width": 188,
+ "height": 282.8
+ },
+ {
+ "width": 188,
+ "height": 276.4
+ },
+ {
+ "width": 188,
+ "height": 270
+ },
+ {
+ "width": 188,
+ "height": 263.6
+ },
+ {
+ "width": 188,
+ "height": 257.2
+ },
+ {
+ "width": 188,
+ "height": 250.8
+ },
+ {
+ "width": 188,
+ "height": 244.4
+ },
+ {
+ "width": 188,
+ "height": 238
+ },
+ {
+ "width": 188,
+ "height": 238
+ },
+ {
+ "width": 188,
+ "height": 231.2
+ },
+ {
+ "width": 188,
+ "height": 224.8
+ },
+ {
+ "width": 188,
+ "height": 218.4
+ },
+ {
+ "width": 188,
+ "height": 212
+ },
+ {
+ "width": 188,
+ "height": 212
+ },
+ {
+ "width": 188,
+ "height": 192.4
+ },
+ {
+ "width": 188,
+ "height": 159.6
+ },
+ {
+ "width": 188,
+ "height": 124.4
+ },
+ {
+ "width": 188,
+ "height": 92.8
+ },
+ {
+ "width": 188,
+ "height": 64.8
+ },
+ {
+ "width": 188,
+ "height": 44.4
+ },
+ {
+ "width": 188,
+ "height": 29.2
+ },
+ {
+ "width": 188,
+ "height": 18.4
+ },
+ {
+ "width": 188,
+ "height": 10.8
+ },
+ {
+ "width": 188,
+ "height": 5.6
+ },
+ {
+ "width": 188,
+ "height": 2.4
+ },
+ {
+ "width": 188,
+ "height": 0.4
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9808927,
+ 0.8211168,
+ 0.61845565,
+ 0.43834114,
+ 0.29850912,
+ 0.19755232,
+ 0.12793064,
+ 0.08142871,
+ 0.051099956,
+ 0.031684637,
+ 0.019442618,
+ 0.011821032,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json
new file mode 100644
index 000000000000..210ff0985e78
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json
@@ -0,0 +1,614 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816,
+ 832,
+ 848,
+ 864,
+ 880,
+ 896,
+ 912,
+ 928
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50.8,
+ "y": 52
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 390
+ },
+ {
+ "width": 188,
+ "height": 390
+ },
+ {
+ "width": 188,
+ "height": 379.2
+ },
+ {
+ "width": 188,
+ "height": 371.2
+ },
+ {
+ "width": 188,
+ "height": 363.2
+ },
+ {
+ "width": 188,
+ "height": 355.2
+ },
+ {
+ "width": 188,
+ "height": 347.2
+ },
+ {
+ "width": 188,
+ "height": 339.2
+ },
+ {
+ "width": 188,
+ "height": 331.2
+ },
+ {
+ "width": 188,
+ "height": 323.2
+ },
+ {
+ "width": 188,
+ "height": 323.2
+ },
+ {
+ "width": 188,
+ "height": 314.8
+ },
+ {
+ "width": 188,
+ "height": 306.8
+ },
+ {
+ "width": 188,
+ "height": 298.8
+ },
+ {
+ "width": 188,
+ "height": 290.8
+ },
+ {
+ "width": 188,
+ "height": 282.8
+ },
+ {
+ "width": 188,
+ "height": 274.8
+ },
+ {
+ "width": 188,
+ "height": 266.8
+ },
+ {
+ "width": 188,
+ "height": 258.8
+ },
+ {
+ "width": 188,
+ "height": 258.8
+ },
+ {
+ "width": 188,
+ "height": 250.4
+ },
+ {
+ "width": 188,
+ "height": 242.4
+ },
+ {
+ "width": 188,
+ "height": 234.4
+ },
+ {
+ "width": 188,
+ "height": 226.4
+ },
+ {
+ "width": 188,
+ "height": 218.4
+ },
+ {
+ "width": 188,
+ "height": 210.4
+ },
+ {
+ "width": 188,
+ "height": 202.4
+ },
+ {
+ "width": 188,
+ "height": 194.4
+ },
+ {
+ "width": 188,
+ "height": 194.4
+ },
+ {
+ "width": 188,
+ "height": 185.6
+ },
+ {
+ "width": 188,
+ "height": 178
+ },
+ {
+ "width": 188,
+ "height": 170
+ },
+ {
+ "width": 188,
+ "height": 161.6
+ },
+ {
+ "width": 188,
+ "height": 161.6
+ },
+ {
+ "width": 188,
+ "height": 144.8
+ },
+ {
+ "width": 188,
+ "height": 118.8
+ },
+ {
+ "width": 188,
+ "height": 92
+ },
+ {
+ "width": 188,
+ "height": 68
+ },
+ {
+ "width": 188,
+ "height": 49.6
+ },
+ {
+ "width": 188,
+ "height": 35.2
+ },
+ {
+ "width": 188,
+ "height": 24.4
+ },
+ {
+ "width": 188,
+ "height": 16.4
+ },
+ {
+ "width": 188,
+ "height": 10.4
+ },
+ {
+ "width": 188,
+ "height": 6.4
+ },
+ {
+ "width": 188,
+ "height": 3.6
+ },
+ {
+ "width": 188,
+ "height": 1.6
+ },
+ {
+ "width": 188,
+ "height": 0.4
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9967737,
+ 0.86538374,
+ 0.66414475,
+ 0.47619528,
+ 0.32686388,
+ 0.21757984,
+ 0.14153665,
+ 0.09041709,
+ 0.05691254,
+ 0.035380244,
+ 0.02175957,
+ 0.01325649,
+ 0.008007765,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json
new file mode 100644
index 000000000000..d186df22dda0
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragOpen.json
@@ -0,0 +1,544 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 188,
+ "height": 1.6
+ },
+ {
+ "width": 188,
+ "height": 1.6
+ },
+ {
+ "width": 188,
+ "height": 3.2
+ },
+ {
+ "width": 188,
+ "height": 4.8
+ },
+ {
+ "width": 188,
+ "height": 6.4
+ },
+ {
+ "width": 188,
+ "height": 8
+ },
+ {
+ "width": 188,
+ "height": 9.6
+ },
+ {
+ "width": 188,
+ "height": 11.2
+ },
+ {
+ "width": 188,
+ "height": 12.8
+ },
+ {
+ "width": 188,
+ "height": 14.4
+ },
+ {
+ "width": 188,
+ "height": 14.4
+ },
+ {
+ "width": 188,
+ "height": 16.4
+ },
+ {
+ "width": 188,
+ "height": 18
+ },
+ {
+ "width": 188,
+ "height": 19.6
+ },
+ {
+ "width": 188,
+ "height": 21.2
+ },
+ {
+ "width": 188,
+ "height": 22.8
+ },
+ {
+ "width": 188,
+ "height": 24.4
+ },
+ {
+ "width": 188,
+ "height": 26
+ },
+ {
+ "width": 188,
+ "height": 27.6
+ },
+ {
+ "width": 188,
+ "height": 27.6
+ },
+ {
+ "width": 188,
+ "height": 29.2
+ },
+ {
+ "width": 188,
+ "height": 30.8
+ },
+ {
+ "width": 188,
+ "height": 32.4
+ },
+ {
+ "width": 188,
+ "height": 34
+ },
+ {
+ "width": 188,
+ "height": 40.4
+ },
+ {
+ "width": 188,
+ "height": 52.4
+ },
+ {
+ "width": 188,
+ "height": 64.8
+ },
+ {
+ "width": 188,
+ "height": 83.2
+ },
+ {
+ "width": 188,
+ "height": 96
+ },
+ {
+ "width": 188,
+ "height": 114.8
+ },
+ {
+ "width": 188,
+ "height": 132
+ },
+ {
+ "width": 188,
+ "height": 148
+ },
+ {
+ "width": 188,
+ "height": 162
+ },
+ {
+ "width": 188,
+ "height": 168.4
+ },
+ {
+ "width": 188,
+ "height": 192.8
+ },
+ {
+ "width": 188,
+ "height": 229.6
+ },
+ {
+ "width": 188,
+ "height": 268
+ },
+ {
+ "width": 188,
+ "height": 302
+ },
+ {
+ "width": 188,
+ "height": 330
+ },
+ {
+ "width": 188,
+ "height": 351.6
+ },
+ {
+ "width": 188,
+ "height": 367.6
+ },
+ {
+ "width": 188,
+ "height": 379.2
+ },
+ {
+ "width": 188,
+ "height": 387.2
+ },
+ {
+ "width": 188,
+ "height": 392.4
+ },
+ {
+ "width": 188,
+ "height": 395.6
+ },
+ {
+ "width": 188,
+ "height": 398
+ },
+ {
+ "width": 188,
+ "height": 398.8
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ },
+ {
+ "width": 188,
+ "height": 400
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.0067873597,
+ 0.0612576,
+ 0.19080025,
+ 0.39327443,
+ 0.5711931,
+ 0.70855826,
+ 0.8074064,
+ 0.8754226,
+ 0.9207788,
+ 0.95032376,
+ 0.9692185,
+ 0.98112255,
+ 0.9885286,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json
new file mode 100644
index 000000000000..a9c24fa87089
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json
@@ -0,0 +1,444 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50.4,
+ "y": 50.8
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 389.6
+ },
+ {
+ "width": 188,
+ "height": 378.8
+ },
+ {
+ "width": 188,
+ "height": 366
+ },
+ {
+ "width": 188,
+ "height": 352
+ },
+ {
+ "width": 188,
+ "height": 352
+ },
+ {
+ "width": 188,
+ "height": 316.8
+ },
+ {
+ "width": 188,
+ "height": 261.2
+ },
+ {
+ "width": 188,
+ "height": 202.8
+ },
+ {
+ "width": 188,
+ "height": 150.8
+ },
+ {
+ "width": 188,
+ "height": 107.6
+ },
+ {
+ "width": 188,
+ "height": 71.2
+ },
+ {
+ "width": 188,
+ "height": 41.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 8.4
+ },
+ {
+ "width": 188,
+ "height": 0.4
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9833227,
+ 0.8263634,
+ 0.623688,
+ 0.44261706,
+ 0.3016883,
+ 0.1997872,
+ 0.12944388,
+ 0.08242595,
+ 0.051743627,
+ 0.032093227,
+ 0.019698441,
+ 0.0119793415,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json
new file mode 100644
index 000000000000..f9279f1fae5c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingOpen.json
@@ -0,0 +1,374 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 188,
+ "height": 2
+ },
+ {
+ "width": 188,
+ "height": 4.4
+ },
+ {
+ "width": 188,
+ "height": 6.8
+ },
+ {
+ "width": 188,
+ "height": 10
+ },
+ {
+ "width": 188,
+ "height": 13.2
+ },
+ {
+ "width": 188,
+ "height": 16.8
+ },
+ {
+ "width": 188,
+ "height": 16.8
+ },
+ {
+ "width": 188,
+ "height": 25.2
+ },
+ {
+ "width": 188,
+ "height": 53.2
+ },
+ {
+ "width": 188,
+ "height": 119.6
+ },
+ {
+ "width": 188,
+ "height": 182
+ },
+ {
+ "width": 188,
+ "height": 235.6
+ },
+ {
+ "width": 188,
+ "height": 279.2
+ },
+ {
+ "width": 188,
+ "height": 313.2
+ },
+ {
+ "width": 188,
+ "height": 338.8
+ },
+ {
+ "width": 188,
+ "height": 357.6
+ },
+ {
+ "width": 188,
+ "height": 371.2
+ },
+ {
+ "width": 188,
+ "height": 380.8
+ },
+ {
+ "width": 188,
+ "height": 387.6
+ },
+ {
+ "width": 188,
+ "height": 392
+ },
+ {
+ "width": 188,
+ "height": 395.2
+ },
+ {
+ "width": 188,
+ "height": 396.8
+ },
+ {
+ "width": 188,
+ "height": 398
+ },
+ {
+ "width": 188,
+ "height": 398.8
+ },
+ {
+ "width": 188,
+ "height": 399.2
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0,
+ 0.008216977,
+ 0.06259775,
+ 0.19032806,
+ 0.39281356,
+ 0.57081985,
+ 0.7082821,
+ 0.80721295,
+ 0.8752918,
+ 0.9206928,
+ 0.95026827,
+ 0.9691833,
+ 0.98110056,
+ 0.988515,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json
new file mode 100644
index 000000000000..2504e57a927b
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_magneticDetachAndReattach.json
@@ -0,0 +1,714 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ 768,
+ 784,
+ 800,
+ 816,
+ 832,
+ 848,
+ 864,
+ 880,
+ 896,
+ 912,
+ 928,
+ 944,
+ 960,
+ 976,
+ 992,
+ 1008,
+ 1024,
+ 1040,
+ 1056,
+ 1072,
+ 1088
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 188,
+ "height": 1.6
+ },
+ {
+ "width": 188,
+ "height": 2.8
+ },
+ {
+ "width": 188,
+ "height": 4
+ },
+ {
+ "width": 188,
+ "height": 5.2
+ },
+ {
+ "width": 188,
+ "height": 6.4
+ },
+ {
+ "width": 188,
+ "height": 7.6
+ },
+ {
+ "width": 188,
+ "height": 8.8
+ },
+ {
+ "width": 188,
+ "height": 10
+ },
+ {
+ "width": 188,
+ "height": 10.8
+ },
+ {
+ "width": 188,
+ "height": 12
+ },
+ {
+ "width": 188,
+ "height": 12.8
+ },
+ {
+ "width": 188,
+ "height": 13.6
+ },
+ {
+ "width": 188,
+ "height": 14.8
+ },
+ {
+ "width": 188,
+ "height": 15.6
+ },
+ {
+ "width": 188,
+ "height": 16.4
+ },
+ {
+ "width": 188,
+ "height": 17.2
+ },
+ {
+ "width": 188,
+ "height": 17.6
+ },
+ {
+ "width": 188,
+ "height": 18.4
+ },
+ {
+ "width": 188,
+ "height": 19.2
+ },
+ {
+ "width": 188,
+ "height": 19.6
+ },
+ {
+ "width": 188,
+ "height": 20
+ },
+ {
+ "width": 188,
+ "height": 20.4
+ },
+ {
+ "width": 188,
+ "height": 20.8
+ },
+ {
+ "width": 188,
+ "height": 21.2
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 21.2
+ },
+ {
+ "width": 188,
+ "height": 20.8
+ },
+ {
+ "width": 188,
+ "height": 20.4
+ },
+ {
+ "width": 188,
+ "height": 20
+ },
+ {
+ "width": 188,
+ "height": 19.6
+ },
+ {
+ "width": 188,
+ "height": 19.2
+ },
+ {
+ "width": 188,
+ "height": 18.4
+ },
+ {
+ "width": 188,
+ "height": 17.6
+ },
+ {
+ "width": 188,
+ "height": 17.2
+ },
+ {
+ "width": 188,
+ "height": 16.4
+ },
+ {
+ "width": 188,
+ "height": 15.6
+ },
+ {
+ "width": 188,
+ "height": 14.8
+ },
+ {
+ "width": 188,
+ "height": 13.6
+ },
+ {
+ "width": 188,
+ "height": 12.8
+ },
+ {
+ "width": 188,
+ "height": 12
+ },
+ {
+ "width": 188,
+ "height": 10.8
+ },
+ {
+ "width": 188,
+ "height": 10
+ },
+ {
+ "width": 188,
+ "height": 8.8
+ },
+ {
+ "width": 188,
+ "height": 7.6
+ },
+ {
+ "width": 188,
+ "height": 6.4
+ },
+ {
+ "width": 188,
+ "height": 5.2
+ },
+ {
+ "width": 188,
+ "height": 4
+ },
+ {
+ "width": 188,
+ "height": 2.8
+ },
+ {
+ "width": 188,
+ "height": 1.6
+ },
+ {
+ "width": 188,
+ "height": 0.4
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.012518823,
+ 0.0741024,
+ 0.2254293,
+ 0.42628878,
+ 0.5976641,
+ 0.7280312,
+ 0.82100236,
+ 0.8845844,
+ 0.9267946,
+ 0.95419544,
+ 0.9716705,
+ 0.98265487,
+ 0.98947525,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.9944124,
+ 0.9417388,
+ 0.8184184,
+ 0.6157812,
+ 0.4361611,
+ 0.2968906,
+ 0.19641554,
+ 0.12716137,
+ 0.080921985,
+ 0.050773025,
+ 0.03147719,
+ 0.019312752,
+ 0.011740655
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json
new file mode 100644
index 000000000000..86fac739372e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealCloseTransition.json
@@ -0,0 +1,314 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 400
+ },
+ {
+ "width": 188,
+ "height": 372
+ },
+ {
+ "width": 188,
+ "height": 312.8
+ },
+ {
+ "width": 188,
+ "height": 246.8
+ },
+ {
+ "width": 188,
+ "height": 185.2
+ },
+ {
+ "width": 188,
+ "height": 133.6
+ },
+ {
+ "width": 188,
+ "height": 93.2
+ },
+ {
+ "width": 188,
+ "height": 58.8
+ },
+ {
+ "width": 188,
+ "height": 34.4
+ },
+ {
+ "width": 188,
+ "height": 18
+ },
+ {
+ "width": 188,
+ "height": 7.6
+ },
+ {
+ "width": 188,
+ "height": 0.8
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 0
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.91758585,
+ 0.72435355,
+ 0.52812576,
+ 0.3665868,
+ 0.24600428,
+ 0.16102076,
+ 0.103373945,
+ 0.06533456,
+ 0.04075712,
+ 0.025142312,
+ 0.015358448,
+ 0.0092999935,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json
new file mode 100644
index 000000000000..ad282f216f8f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_triggeredRevealOpenTransition.json
@@ -0,0 +1,244 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336
+ ],
+ "features": [
+ {
+ "name": "RevealElement_position",
+ "type": "dpOffset",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ },
+ {
+ "x": 50,
+ "y": 50
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_size",
+ "type": "dpSize",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "width": 188,
+ "height": 0
+ },
+ {
+ "width": 188,
+ "height": 6.8
+ },
+ {
+ "width": 188,
+ "height": 21.6
+ },
+ {
+ "width": 188,
+ "height": 52.8
+ },
+ {
+ "width": 188,
+ "height": 129.6
+ },
+ {
+ "width": 188,
+ "height": 196.4
+ },
+ {
+ "width": 188,
+ "height": 250.4
+ },
+ {
+ "width": 188,
+ "height": 293.2
+ },
+ {
+ "width": 188,
+ "height": 325.2
+ },
+ {
+ "width": 188,
+ "height": 348.8
+ },
+ {
+ "width": 188,
+ "height": 365.6
+ },
+ {
+ "width": 188,
+ "height": 377.2
+ },
+ {
+ "width": 188,
+ "height": 385.6
+ },
+ {
+ "width": 188,
+ "height": 391.2
+ },
+ {
+ "width": 188,
+ "height": 394.8
+ },
+ {
+ "width": 188,
+ "height": 396.8
+ },
+ {
+ "width": 188,
+ "height": 398
+ },
+ {
+ "width": 188,
+ "height": 398.8
+ },
+ {
+ "width": 188,
+ "height": 399.2
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ },
+ {
+ "width": 188,
+ "height": 399.6
+ }
+ ]
+ },
+ {
+ "name": "RevealElement_alpha",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ 0,
+ 0.05698657,
+ 0.24197984,
+ 0.44158113,
+ 0.6097554,
+ 0.73685503,
+ 0.8271309,
+ 0.8886989,
+ 0.9294886,
+ 0.9559254,
+ 0.97276413,
+ 0.98333716,
+ 0.98989624,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json b/packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json
new file mode 100644
index 000000000000..a2f8863a34f7
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json
@@ -0,0 +1,68 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "Foo_yOffset",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 125,
+ 125,
+ 124.28647,
+ 107.02858,
+ 81.95502,
+ 0,
+ 0,
+ 0,
+ 0,
+ 7.2947845,
+ 30.375374,
+ 55.12497,
+ 75.944496,
+ 91.69751,
+ 102.92622,
+ 110.62873,
+ 115.772865,
+ 119.141266,
+ 121.313736,
+ 122.69816,
+ 123.57184,
+ 124.11877,
+ {
+ "type": "not_found"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json
new file mode 100644
index 000000000000..bda53bbf3e6c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json
@@ -0,0 +1,48 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "Foo_yOffset",
+ "type": "float",
+ "data_points": [
+ 125,
+ 125,
+ 125,
+ 125,
+ 111.61491,
+ 86.9892,
+ 63.112034,
+ 43.8049,
+ 29.501678,
+ 19.439606,
+ 12.599068,
+ 8.06028,
+ 5.102936,
+ 3.2029724,
+ 1.9959946,
+ 1.2362518,
+ 0.761673,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json
new file mode 100644
index 000000000000..7def82f08d0c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json
@@ -0,0 +1,26 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "Foo_yOffset",
+ "type": "float",
+ "data_points": [
+ 125,
+ 104.166664,
+ 83.33333,
+ 62.5,
+ 41.666664,
+ 20.833336,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json
index 2df440912bfc..054b4a100dea 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json
@@ -20,7 +20,7 @@
"height": 100
},
{
- "width": 125.14286,
+ "width": 125.2,
"height": 90
},
{
@@ -28,7 +28,7 @@
"height": 80
},
{
- "width": 175.14285,
+ "width": 175.2,
"height": 70
},
{
diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json
index 2b0a9541a394..ad46a8d14ede 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json
@@ -21,7 +21,7 @@
"height": 100
},
{
- "width": 125.14286,
+ "width": 125.2,
"height": 90
},
{
@@ -29,7 +29,7 @@
"height": 80
},
{
- "width": 175.14285,
+ "width": 175.2,
"height": 70
},
{
diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json
index 027df299d15e..9a97053eb821 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json
@@ -20,7 +20,7 @@
"height": 60
},
{
- "width": 125.14286,
+ "width": 125.2,
"height": 60
},
{
@@ -28,7 +28,7 @@
"height": 60
},
{
- "width": 175.14285,
+ "width": 175.2,
"height": 60
},
{
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index fa7661b6d102..6538d4340cf3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -45,6 +45,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -64,10 +65,13 @@ import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -582,4 +586,34 @@ class SceneTransitionLayoutTest {
assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
.isEqualTo(motionScheme2)
}
+
+ @Test
+ fun alwaysCompose() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state) {
+ scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
+ scene(SceneB, alwaysCompose = true) {
+ Box(Modifier.element(TestElements.Bar).size(40.dp))
+ }
+ }
+ }
+
+ // Idle(A): Foo is displayed and Bar exists given that SceneB is always composed but it is
+ // not displayed.
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertExists().assertIsNotDisplayed()
+
+ // Transition(A => B): Foo and Bar are both displayed
+ val aToB = transition(SceneA, SceneB)
+ scope.launch { state.startTransition(aToB) }
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+
+ // Idle(B): Foo does not exist and Bar is displayed.
+ aToB.finish()
+ rule.onNode(isElement(TestElements.Foo)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt
new file mode 100644
index 000000000000..9d403507252f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.mechanics
+
+import android.platform.test.annotations.MotionTest
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
+import com.android.compose.animation.scene.SceneTransitionsBuilder
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TestOverlays
+import com.android.compose.animation.scene.TestScenes
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.TransitionRecordingSpec
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.featureOfElement
+import com.android.compose.animation.scene.mechanics.TransitionScopedMechanicsAdapter.Companion.appearDirection
+import com.android.compose.animation.scene.recordTransition
+import com.android.compose.animation.scene.testing.lastOffsetForTesting
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import com.android.compose.animation.scene.transitions
+import com.android.mechanics.spec.InputDirection
+import com.android.mechanics.spec.Mapping
+import com.android.mechanics.spec.MotionSpec
+import com.android.mechanics.spec.buildDirectionalMotionSpec
+import com.android.mechanics.spring.SpringParameters
+import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.TestScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.compose.ComposeRecordingSpec
+import platform.test.motion.compose.MotionControl
+import platform.test.motion.compose.createFixedConfigurationComposeMotionTestRule
+import platform.test.motion.compose.recordMotion
+import platform.test.motion.compose.runTest
+import platform.test.motion.golden.DataPoint
+import platform.test.motion.golden.DataPointTypes
+import platform.test.motion.golden.FeatureCapture
+import platform.test.motion.testing.createGoldenPathManager
+
+@RunWith(AndroidJUnit4::class)
+@MotionTest
+class TransitionScopedMechanicsAdapterTest {
+
+ private val goldenPaths =
+ createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens")
+
+ private val testScope = TestScope()
+ @get:Rule val motionRule = createFixedConfigurationComposeMotionTestRule(goldenPaths, testScope)
+ private val composeRule = motionRule.toolkit.composeContentTestRule
+
+ @Test
+ fun motionValue_withoutAnimation_terminatesImmediately() =
+ motionRule.runTest {
+ val specFactory: SpecFactory = { _, _ ->
+ MotionSpec(
+ // Linearly animate from 10 down to 0
+ buildDirectionalMotionSpec(TestSpring, Mapping.Fixed(50.dp.toPx())) {
+ targetFromCurrent(breakpoint = 0f, to = 0f)
+ constantValueFromCurrent(breakpoint = 1f)
+ }
+ )
+ }
+
+ assertOffsetMatchesGolden(
+ transition = {
+ spec = tween(16 * 6, easing = LinearEasing)
+ transformation(TestElements.Foo) { TestTransformation(specFactory) }
+ }
+ )
+ }
+
+ @Test
+ fun motionValue_withAnimation_prolongsTransition() =
+ motionRule.runTest {
+ val specFactory: SpecFactory = { _, _ ->
+ MotionSpec(
+ // Use a spring to toggle 10f -> 0f at a progress of 0.5
+ buildDirectionalMotionSpec(TestSpring, Mapping.Fixed(50.dp.toPx())) {
+ constantValue(breakpoint = 0.5f, value = 0f)
+ }
+ )
+ }
+
+ assertOffsetMatchesGolden(
+ transition = {
+ spec = tween(16 * 6, easing = LinearEasing)
+ transformation(TestElements.Foo) { TestTransformation(specFactory) }
+ }
+ )
+ }
+
+ @Test
+ fun motionValue_interruptedAnimation_completes() =
+ motionRule.runTest {
+ val transitions = transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) {
+ spec = tween(16 * 6, easing = LinearEasing)
+
+ transformation(TestElements.Foo) {
+ TestTransformation { _, _ ->
+ MotionSpec(
+ buildDirectionalMotionSpec(
+ TestSpring,
+ Mapping.Fixed(50.dp.toPx()),
+ ) {
+ constantValue(breakpoint = 0.3f, value = 0f)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ val state =
+ composeRule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(TestScenes.SceneA, transitions)
+ }
+ lateinit var coroutineScope: CoroutineScope
+
+ val motionControl =
+ MotionControl(delayRecording = { awaitFrames(4) }) {
+ awaitFrames(1)
+ val (transitionToB, firstTransitionJob) =
+ toolkit.composeContentTestRule.runOnUiThread {
+ checkNotNull(
+ state.setTargetScene(
+ TestScenes.SceneB,
+ animationScope = coroutineScope,
+ )
+ )
+ }
+
+ awaitCondition { transitionToB.progress > 0.5f }
+ val (transitionBackToA, secondTransitionJob) =
+ toolkit.composeContentTestRule.runOnUiThread {
+ checkNotNull(
+ state.setTargetScene(
+ TestScenes.SceneA,
+ animationScope = coroutineScope,
+ )
+ )
+ }
+
+ Truth.assertThat(transitionBackToA.replacedTransition)
+ .isSameInstanceAs(transitionToB)
+
+ awaitCondition { !state.isTransitioning() }
+
+ Truth.assertThat(firstTransitionJob.isCompleted).isTrue()
+ Truth.assertThat(secondTransitionJob.isCompleted).isTrue()
+ }
+
+ val motion =
+ recordMotion(
+ content = {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayoutForTesting(state, modifier = Modifier.size(50.dp)) {
+ scene(TestScenes.SceneA) { SceneAContent() }
+ scene(TestScenes.SceneB) { SceneBContent() }
+ }
+ },
+ ComposeRecordingSpec(motionControl, recordBefore = false) {
+ featureOfElement(TestElements.Foo, yOffsetFeature)
+ },
+ )
+
+ assertThat(motion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun animationDirection_sceneTransition_forward() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { it(TestElements.Foo) }
+ },
+ ) { state, animationScope, _ ->
+ state.setTargetScene(TestScenes.SceneB, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Max)
+ }
+
+ @Test
+ fun animationDirection_sceneTransition_backwards() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneB,
+ transitionBuilder = {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { it(TestElements.Foo) }
+ },
+ ) { state, animationScope, _ ->
+ state.setTargetScene(TestScenes.SceneA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_interruptedTransition_flipsDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { it(TestElements.Foo) }
+ },
+ ) { state, animationScope, iteration ->
+ when (iteration) {
+ 0 -> {
+ state.setTargetScene(TestScenes.SceneB, animationScope)
+ true
+ }
+ 1 -> {
+ state.setTargetScene(TestScenes.SceneA, animationScope)
+ false
+ }
+ else -> throw AssertionError()
+ }
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_showOverlay_animatesInMaxDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.showOverlay(TestOverlays.OverlayA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Max)
+ }
+
+ @Test
+ fun animationDirection_hideOverlay_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayA),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.hideOverlay(TestOverlays.OverlayA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_hideOverlayMidTransition_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, iteration ->
+ when (iteration) {
+ 0 -> {
+ state.showOverlay(TestOverlays.OverlayA, animationScope)
+ true
+ }
+ 1 -> {
+ state.hideOverlay(TestOverlays.OverlayA, animationScope)
+ false
+ }
+ else -> throw AssertionError()
+ }
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_replaceOverlay_showingContent_animatesInMaxDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayB),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.replaceOverlay(TestOverlays.OverlayB, TestOverlays.OverlayA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Max)
+ }
+
+ @Test
+ fun animationDirection_replaceOverlay_hidingContent_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayA),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.replaceOverlay(TestOverlays.OverlayA, TestOverlays.OverlayB, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_replaceOverlay_revertMidTransition_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayB),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, iteration ->
+ when (iteration) {
+ 0 -> {
+ state.replaceOverlay(
+ TestOverlays.OverlayB,
+ TestOverlays.OverlayA,
+ animationScope,
+ )
+ true
+ }
+ 1 -> {
+ state.replaceOverlay(
+ TestOverlays.OverlayA,
+ TestOverlays.OverlayB,
+ animationScope,
+ )
+ false
+ }
+ else -> throw AssertionError()
+ }
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ private fun ComposeContentTestRule.getAppearDirectionOnTransition(
+ initialScene: SceneKey,
+ transitionBuilder: SceneTransitionsBuilder.(foo: DirectionAssertionTransition) -> Unit,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ runTransition:
+ (
+ state: MutableSceneTransitionLayoutStateImpl,
+ animationScope: CoroutineScope,
+ iteration: Int,
+ ) -> Boolean,
+ ): InputDirection {
+
+ lateinit var result: InputDirection
+
+ val x: DirectionAssertionTransition = {
+ transformation(it) {
+ object : CustomPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): IntSize {
+ result = appearDirection(content, element, transition)
+ return IntSize.Zero
+ }
+ }
+ }
+ }
+
+ val state = runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(
+ initialScene,
+ transitions { transitionBuilder(x) },
+ initialOverlays,
+ )
+ }
+ lateinit var coroutineScope: CoroutineScope
+
+ setContent {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(state) {
+ scene(TestScenes.SceneA) { SceneAContent() }
+ scene(TestScenes.SceneB) { SceneBContent() }
+ overlay(TestOverlays.OverlayA) { OverlayAContent() }
+ overlay(TestOverlays.OverlayB) {}
+ }
+ }
+
+ waitForIdle()
+ mainClock.autoAdvance = false
+ var keepOnAnimating = true
+ var iterationCount = 0
+ while (keepOnAnimating) {
+ runOnUiThread { keepOnAnimating = runTransition(state, coroutineScope, iterationCount) }
+ composeRule.mainClock.advanceTimeByFrame()
+ waitForIdle()
+ iterationCount++
+ }
+ waitForIdle()
+
+ return result
+ }
+
+ private class TestTransformation(specFactory: SpecFactory) :
+ CustomPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
+ val motionValue =
+ TransitionScopedMechanicsAdapter(createSpec = specFactory, stableThreshold = 1f)
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): Offset {
+ val yOffset =
+ with(motionValue) { update(content, element, transition, transitionScope) }
+
+ return Offset(x = 0f, y = yOffset)
+ }
+ }
+
+ private fun assertOffsetMatchesGolden(transition: TransitionBuilder.() -> Unit) {
+ val recordingSpec =
+ TransitionRecordingSpec(recordBefore = false, recordAfter = true) {
+ featureOfElement(TestElements.Foo, yOffsetFeature)
+ }
+
+ val motion =
+ motionRule.recordTransition(
+ fromSceneContent = { SceneAContent() },
+ toSceneContent = { SceneBContent() },
+ transition = transition,
+ recordingSpec = recordingSpec,
+ layoutModifier = Modifier.size(50.dp),
+ )
+
+ motionRule.assertThat(motion).timeSeriesMatchesGolden()
+ }
+
+ companion object {
+
+ @Composable
+ fun ContentScope.SceneAContent() {
+ Box(modifier = Modifier.fillMaxSize())
+ }
+
+ @Composable
+ fun ContentScope.SceneBContent() {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Box(Modifier.element(TestElements.Foo).size(50.dp).background(Color.Red))
+ }
+ }
+
+ @Composable
+ fun ContentScope.OverlayAContent() {
+ Box(Modifier.element(TestElements.Bar).size(50.dp).background(Color.Red))
+ }
+
+ @Composable
+ fun ContentScope.OverlayBContent() {
+ Box(modifier = Modifier.size(50.dp).background(Color.Green))
+ }
+
+ val TestSpring = SpringParameters(1200f, 1f)
+
+ val yOffsetFeature =
+ FeatureCapture<SemanticsNode, Float>("yOffset") {
+ DataPoint.of(it.lastOffsetForTesting?.y, DataPointTypes.float)
+ }
+ }
+}
+
+typealias DirectionAssertionTransition = TransitionBuilder.(container: ElementKey) -> Unit
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt
new file mode 100644
index 000000000000..ed73d100dc6b
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.reveal
+
+import android.platform.test.annotations.MotionTest
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.swipe
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.FeatureCaptures.elementAlpha
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.featureOfElement
+import com.android.compose.animation.scene.transitions
+import com.android.mechanics.behavior.VerticalExpandContainerSpec
+import com.android.mechanics.behavior.verticalExpandContainerBackground
+import kotlin.math.sin
+import kotlinx.coroutines.CoroutineScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.compose.ComposeFeatureCaptures.dpSize
+import platform.test.motion.compose.ComposeFeatureCaptures.positionInRoot
+import platform.test.motion.compose.ComposeRecordingSpec
+import platform.test.motion.compose.MotionControl
+import platform.test.motion.compose.MotionControlScope
+import platform.test.motion.compose.createFixedConfigurationComposeMotionTestRule
+import platform.test.motion.compose.recordMotion
+import platform.test.motion.compose.runTest
+import platform.test.motion.testing.createGoldenPathManager
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.PathConfig
+import platform.test.screenshot.PathElementNoContext
+
+@MotionTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class ContentRevealTest(private val isFloating: Boolean) {
+
+ private val pathConfig =
+ PathConfig(
+ PathElementNoContext("floating", isDir = false) {
+ if (isFloating) "floating" else "edge"
+ }
+ )
+
+ private val goldenPaths =
+ createGoldenPathManager(
+ "frameworks/base/packages/SystemUI/compose/scene/tests/goldens",
+ pathConfig,
+ )
+
+ @get:Rule val motionRule = createFixedConfigurationComposeMotionTestRule(goldenPaths)
+
+ private val fakeHaptics = FakeHaptics()
+
+ private val motionSpec = VerticalExpandContainerSpec(isFloating)
+
+ @Test
+ fun verticalReveal_triggeredRevealOpenTransition() {
+ assertVerticalContainerRevealMotion(
+ TriggeredRevealMotion(SceneClosed, SceneOpen),
+ "verticalReveal_triggeredRevealOpenTransition",
+ )
+ }
+
+ @Test
+ fun verticalReveal_triggeredRevealCloseTransition() {
+ assertVerticalContainerRevealMotion(
+ TriggeredRevealMotion(SceneOpen, SceneClosed),
+ "verticalReveal_triggeredRevealCloseTransition",
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_magneticDetachAndReattach() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneClosed) {
+ val gestureDurationMillis = 1000L
+ swipe(
+ curve = {
+ val progress = it / gestureDurationMillis.toFloat()
+ val y = sin(progress * Math.PI).toFloat() * 100.dp.toPx()
+ Offset(centerX, y)
+ },
+ gestureDurationMillis,
+ )
+ },
+ "verticalReveal_gesture_magneticDetachAndReattach",
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_dragOpen() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneClosed) {
+ swipeDown(endY = 200.dp.toPx(), durationMillis = 500)
+ },
+ "verticalReveal_gesture_dragOpen",
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_flingOpen() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneClosed) {
+ val end = Offset(centerX, 80.dp.toPx())
+ swipeWithVelocity(start = topCenter, end = end, endVelocity = FlingVelocity.toPx())
+ },
+ "verticalReveal_gesture_flingOpen",
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_dragFullyClose() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneOpen) {
+ swipeUp(200.dp.toPx(), 0.dp.toPx(), durationMillis = 500)
+ },
+ "verticalReveal_gesture_dragFullyClose",
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_dragHalfClose() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneOpen) {
+ swipeUp(350.dp.toPx(), 100.dp.toPx(), durationMillis = 500)
+ },
+ "verticalReveal_gesture_dragHalfClose",
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_flingClose() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneOpen) {
+ val start = Offset(centerX, 260.dp.toPx())
+ val end = Offset(centerX, 200.dp.toPx())
+ swipeWithVelocity(start, end, FlingVelocity.toPx())
+ },
+ "verticalReveal_gesture_flingClose",
+ )
+ }
+
+ private interface RevealMotion {
+ val startScene: SceneKey
+ }
+
+ private class TriggeredRevealMotion(
+ override val startScene: SceneKey,
+ val targetScene: SceneKey,
+ ) : RevealMotion
+
+ private class GestureRevealMotion(
+ override val startScene: SceneKey,
+ val gestureControl: TouchInjectionScope.() -> Unit,
+ ) : RevealMotion
+
+ private fun assertVerticalContainerRevealMotion(
+ testInstructions: RevealMotion,
+ goldenName: String,
+ ) =
+ motionRule.runTest {
+ val transitions = transitions {
+ from(SceneClosed, to = SceneOpen) {
+ verticalContainerReveal(RevealElement, motionSpec, fakeHaptics)
+ }
+ }
+
+ val state =
+ toolkit.composeContentTestRule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(
+ testInstructions.startScene,
+ transitions,
+ )
+ }
+ lateinit var coroutineScope: CoroutineScope
+
+ val recordTransition: suspend MotionControlScope.() -> Unit = {
+ when (testInstructions) {
+ is TriggeredRevealMotion -> {
+ val transition =
+ toolkit.composeContentTestRule.runOnUiThread {
+ state.setTargetScene(
+ testInstructions.targetScene,
+ animationScope = coroutineScope,
+ )
+ }
+ checkNotNull(transition).second.join()
+ }
+
+ is GestureRevealMotion -> {
+ performTouchInputAsync(
+ onNodeWithTag("stl"),
+ testInstructions.gestureControl,
+ )
+ awaitCondition { !state.isTransitioning() }
+ }
+ }
+ }
+ val recordingSpec =
+ ComposeRecordingSpec(
+ recordBefore = false,
+ recordAfter = false,
+ motionControl = MotionControl(recording = recordTransition),
+ ) {
+ featureOfElement(RevealElement, positionInRoot)
+ featureOfElement(RevealElement, dpSize)
+ featureOfElement(RevealElement, elementAlpha)
+ }
+
+ val motion =
+ recordMotion(
+ content = {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayoutForTesting(
+ state,
+ modifier =
+ Modifier.padding(50.dp)
+ .background(Color.Yellow)
+ .size(ContainerSize.width, ContainerSize.height + 200.dp)
+ .testTag("stl"),
+ ) {
+ scene(
+ SceneClosed,
+ mapOf(Swipe.Down to SceneOpen),
+ content = { ClosedContainer() },
+ )
+ scene(
+ SceneOpen,
+ mapOf(Swipe.Up to SceneClosed),
+ content = { OpenContainer() },
+ )
+ }
+ },
+ recordingSpec,
+ )
+
+ assertThat(motion).timeSeriesMatchesGolden(goldenName)
+ }
+
+ @Composable
+ fun ContentScope.ClosedContainer() {
+ Box(modifier = Modifier.fillMaxSize())
+ }
+
+ @Composable
+ fun ContentScope.OpenContainer() {
+ Box(contentAlignment = Alignment.TopCenter, modifier = Modifier.fillMaxSize()) {
+ Box(
+ modifier =
+ Modifier.element(RevealElement)
+ .size(ContainerSize)
+ .verticalExpandContainerBackground(Color.DarkGray, motionSpec)
+ )
+ }
+ }
+
+ private class FakeHaptics : ContainerRevealHaptics {
+ override fun onRevealThresholdCrossed(revealed: Boolean) {}
+ }
+
+ companion object {
+ @get:Parameters @JvmStatic val parameterValues = listOf(true, false)
+
+ val ContainerSize = DpSize(200.dp, 400.dp)
+
+ val FlingVelocity = 1000.dp // dp/sec
+
+ val SceneClosed = SceneKey("SceneA")
+ val SceneOpen = SceneKey("SceneB")
+
+ val RevealElement = ElementKey("RevealElement")
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 0bd51cd9822d..44f2353dcb75 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -111,8 +111,8 @@ private constructor(metadata: FailureMetadata, private val actual: TransitionSta
}
companion object {
- fun transitionStates() = Factory { metadata, actual: TransitionState ->
- TransitionStateSubject(metadata, actual)
+ fun transitionStates() = Factory { metadata, actual: TransitionState? ->
+ TransitionStateSubject(metadata, actual!!)
}
}
}
@@ -181,8 +181,8 @@ private constructor(metadata: FailureMetadata, actual: TransitionState.Transitio
companion object {
fun sceneTransitions() =
- Factory { metadata, actual: TransitionState.Transition.ChangeScene ->
- SceneTransitionSubject(metadata, actual)
+ Factory { metadata, actual: TransitionState.Transition.ChangeScene? ->
+ SceneTransitionSubject(metadata, actual!!)
}
}
}
@@ -202,8 +202,8 @@ private constructor(
companion object {
fun showOrHideOverlayTransitions() =
- Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay ->
- ShowOrHideOverlayTransitionSubject(metadata, actual)
+ Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay? ->
+ ShowOrHideOverlayTransitionSubject(metadata, actual!!)
}
}
}
@@ -221,8 +221,8 @@ private constructor(metadata: FailureMetadata, actual: TransitionState.Transitio
companion object {
fun replaceOverlayTransitions() =
- Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay ->
- ReplaceOverlayTransitionSubject(metadata, actual)
+ Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay? ->
+ ReplaceOverlayTransitionSubject(metadata, actual!!)
}
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
index ea6f208d6bb9..3b008ac4c1df 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
@@ -31,27 +31,21 @@ import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.TransitionRecordingSpec
import com.android.compose.animation.scene.featureOfElement
import com.android.compose.animation.scene.recordTransition
-import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import platform.test.motion.compose.ComposeFeatureCaptures
-import platform.test.motion.compose.createComposeMotionTestRule
+import platform.test.motion.compose.createFixedConfigurationComposeMotionTestRule
import platform.test.motion.testing.createGoldenPathManager
-import platform.test.screenshot.ResetDeviceEmulationRule
@RunWith(AndroidJUnit4::class)
@MotionTest
class AnchoredSizeTest {
- companion object {
- @JvmField @ClassRule val cleanupRule: ResetDeviceEmulationRule = ResetDeviceEmulationRule()
- }
-
private val goldenPaths =
createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens")
- @get:Rule val motionRule = createComposeMotionTestRule(goldenPaths)
+ @get:Rule val motionRule = createFixedConfigurationComposeMotionTestRule(goldenPaths)
@Test
fun testAnchoredSizeEnter() {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
index ab31038fac8f..368a333fbd78 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
@@ -49,8 +49,8 @@ class DpOffsetSubject(metadata: FailureMetadata, private val actual: DpOffset) :
val DefaultTolerance = Dp(.5f)
fun dpOffsets() =
- Factory<DpOffsetSubject, DpOffset> { metadata, actual ->
- DpOffsetSubject(metadata, actual)
+ Factory { metadata, actual: DpOffset? ->
+ DpOffsetSubject(metadata, actual!!)
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 4bf0ceb51784..2f819d183bcc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -34,12 +34,14 @@ import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.animation.TextAnimatorListener
import com.android.systemui.animation.TypefaceVariantCacheImpl
import com.android.systemui.customization.R
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
-import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.ClockLogger
+import com.android.systemui.plugins.clocks.ClockLogger.Companion.escapeTime
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
@@ -67,7 +69,7 @@ constructor(
var messageBuffer: MessageBuffer
get() = logger.buffer
set(value) {
- logger = Logger(value, TAG)
+ logger = ClockLogger(this, value, TAG)
}
var hasCustomPositionUpdatedAnimation: Boolean = false
@@ -99,7 +101,13 @@ constructor(
@VisibleForTesting
var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
val cache = TypefaceVariantCacheImpl(layout.paint.typeface, NUM_CLOCK_FONT_ANIMATION_STEPS)
- TextAnimator(layout, cache, invalidateCb)
+ TextAnimator(
+ layout,
+ cache,
+ object : TextAnimatorListener {
+ override fun onInvalidate() = invalidateCb()
+ },
+ )
}
// Used by screenshot tests to provide stability
@@ -185,7 +193,9 @@ constructor(
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
- logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
+ logger.d({ "refreshTime: new formattedText=${escapeTime(str1)}" }) {
+ str1 = formattedText?.toString()
+ }
// Setting text actually triggers a layout pass in TextView (because the text view is set to
// wrap_content width and TextView always relayouts for this). This avoids needless relayout
@@ -195,7 +205,7 @@ constructor(
}
text = formattedText
- logger.d({ "refreshTime: done setting new time text to: $str1" }) {
+ logger.d({ "refreshTime: done setting new time text to: ${escapeTime(str1)}" }) {
str1 = formattedText?.toString()
}
@@ -225,7 +235,7 @@ constructor(
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- logger.d("onMeasure")
+ logger.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (!isSingleLineInternal && MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
// Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
@@ -263,14 +273,14 @@ constructor(
canvas.translate(parentWidth / 4f, 0f)
}
- logger.d({ "onDraw($str1)" }) { str1 = text.toString() }
+ logger.onDraw("$text")
// intentionally doesn't call super.onDraw here or else the text will be rendered twice
textAnimator?.draw(canvas)
canvas.restore()
}
override fun invalidate() {
- logger.d("invalidate")
+ logger.invalidate()
super.invalidate()
}
@@ -280,7 +290,7 @@ constructor(
lengthBefore: Int,
lengthAfter: Int,
) {
- logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
+ logger.d({ "onTextChanged(${escapeTime(str1)})" }) { str1 = "$text" }
super.onTextChanged(text, start, lengthBefore, lengthAfter)
}
@@ -370,7 +380,7 @@ constructor(
return
}
- logger.d("animateCharge")
+ logger.animateCharge()
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -394,7 +404,7 @@ constructor(
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- logger.d("animateDoze")
+ logger.animateDoze(isDozing, animate)
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
color = if (isDozing) dozingColor else lockScreenColor,
@@ -484,7 +494,7 @@ constructor(
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
- logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
+ logger.d({ "refreshFormat(${escapeTime(str1)})" }) { str1 = format?.toString() }
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
refreshTime()
@@ -634,7 +644,7 @@ constructor(
companion object {
private val TAG = AnimatableClockView::class.simpleName!!
- private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG)
+ private val DEFAULT_LOGGER = ClockLogger(null, LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG)
const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt
index dd1599e5259d..9857d7f3d69d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shared.clocks
import android.graphics.Canvas
+import com.android.systemui.plugins.clocks.VPoint
+import com.android.systemui.plugins.clocks.VPointF
object CanvasUtil {
fun Canvas.translate(pt: VPointF) = this.translate(pt.x, pt.y)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
index e9e61a718f08..5f71b19fbc3f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -22,10 +22,10 @@ import com.android.app.animation.Interpolators
import com.android.systemui.log.core.Logger
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -111,7 +111,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) :
override fun onZenDataChanged(data: ZenData) {}
- override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ override fun onFontAxesChanged(axes: ClockAxisStyle) {
view.updateAxes(axes)
}
@@ -161,15 +161,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) :
}
override fun onThemeChanged(theme: ThemeConfig) {
- val color =
- when {
- theme.seedColor != null -> theme.seedColor!!
- theme.isDarkTheme ->
- clockCtx.resources.getColor(android.R.color.system_accent1_100)
- else -> clockCtx.resources.getColor(android.R.color.system_accent2_600)
- }
-
- view.updateColor(color)
+ view.updateColor(theme.getDefaultColor(clockCtx.context))
}
override fun onFontSettingChanged(fontSizePx: Float) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 9bb3bac824e9..3cfa78d17fe7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -17,7 +17,6 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Rect
-import android.graphics.RectF
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
@@ -27,13 +26,14 @@ import com.android.systemui.customization.R
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEventListener
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
@@ -102,7 +102,7 @@ class DefaultClockController(
isDarkTheme: Boolean,
dozeFraction: Float,
foldFraction: Float,
- onBoundsChanged: (RectF) -> Unit,
+ clockListener: ClockEventListener?,
) {
largeClock.recomputePadding(null)
@@ -149,14 +149,7 @@ class DefaultClockController(
override fun onThemeChanged(theme: ThemeConfig) {
this@DefaultClockFaceController.theme = theme
- val color =
- when {
- theme.seedColor != null -> theme.seedColor!!
- theme.isDarkTheme ->
- resources.getColor(android.R.color.system_accent1_100)
- else -> resources.getColor(android.R.color.system_accent2_600)
- }
-
+ val color = theme.getDefaultColor(ctx)
if (currentColor == color) {
return
}
@@ -239,7 +232,7 @@ class DefaultClockController(
override fun onZenDataChanged(data: ZenData) {}
- override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {}
+ override fun onFontAxesChanged(axes: ClockAxisStyle) {}
}
open inner class DefaultClockAnimations(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index c3935e68ca04..d778bc04ab8e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -20,6 +20,8 @@ import android.os.Vibrator
import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AxisPresetConfig
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge
import com.android.systemui.plugins.clocks.ClockLogger
@@ -28,7 +30,7 @@ import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
-import com.android.systemui.shared.clocks.FlexClockController.Companion.AXIS_PRESETS
+import com.android.systemui.shared.clocks.FlexClockController.Companion.buildPresetGroup
import com.android.systemui.shared.clocks.FlexClockController.Companion.getDefaultAxes
private val TAG = DefaultClockProvider::class.simpleName
@@ -80,7 +82,7 @@ class DefaultClockProvider(
return if (isClockReactiveVariantsEnabled) {
val buffers = messageBuffers ?: ClockMessageBuffers(ClockLogger.DEFAULT_MESSAGE_BUFFER)
val fontAxes = getDefaultAxes(settings).merge(settings.axes)
- val clockSettings = settings.copy(axes = fontAxes.map { it.toSetting() })
+ val clockSettings = settings.copy(axes = ClockAxisStyle(fontAxes))
val typefaceCache =
TypefaceCache(buffers.infraMessageBuffer, NUM_CLOCK_FONT_ANIMATION_STEPS) {
FLEX_TYPEFACE
@@ -106,17 +108,35 @@ class DefaultClockProvider(
throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- return ClockPickerConfig(
- settings.clockId ?: DEFAULT_CLOCK_ID,
- resources.getString(R.string.clock_default_name),
- resources.getString(R.string.clock_default_description),
- resources.getDrawable(R.drawable.clock_default_thumbnail, null),
- isReactiveToTone = true,
- axes =
- if (!isClockReactiveVariantsEnabled) emptyList()
- else getDefaultAxes(settings).merge(settings.axes),
- axisPresets = if (!isClockReactiveVariantsEnabled) emptyList() else AXIS_PRESETS,
- )
+ if (!isClockReactiveVariantsEnabled) {
+ return ClockPickerConfig(
+ settings.clockId ?: DEFAULT_CLOCK_ID,
+ resources.getString(R.string.clock_default_name),
+ resources.getString(R.string.clock_default_description),
+ resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+ isReactiveToTone = true,
+ axes = emptyList(),
+ presetConfig = null,
+ )
+ } else {
+ val fontAxes = getDefaultAxes(settings).merge(settings.axes)
+ return ClockPickerConfig(
+ settings.clockId ?: DEFAULT_CLOCK_ID,
+ resources.getString(R.string.clock_default_name),
+ resources.getString(R.string.clock_default_description),
+ resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+ isReactiveToTone = true,
+ axes = fontAxes,
+ presetConfig =
+ AxisPresetConfig(
+ listOf(
+ buildPresetGroup(resources, isRound = true),
+ buildPresetGroup(resources, isRound = false),
+ )
+ )
+ .let { cfg -> cfg.copy(current = cfg.findStyle(ClockAxisStyle(fontAxes))) },
+ )
+ }
}
companion object {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
index f5ccc52c8c6b..941cebfb4014 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
@@ -20,7 +20,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.animation.ValueAnimator
-import com.android.systemui.shared.clocks.VPointF.Companion.times
+import com.android.systemui.plugins.clocks.VPointF
+import com.android.systemui.plugins.clocks.VPointF.Companion.times
class DigitTranslateAnimator(private val updateCallback: (VPointF) -> Unit) {
var currentTranslation = VPointF.ZERO
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index 6dfd2268005f..96c3ac75587e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -16,20 +16,24 @@
package com.android.systemui.shared.clocks
-import android.graphics.RectF
+import android.content.res.Resources
import com.android.systemui.animation.GSFAxes
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.AxisPresetConfig
import com.android.systemui.plugins.clocks.AxisType
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEventListener
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.FontUtils.put
+import com.android.systemui.shared.clocks.FontUtils.toClockAxis
import com.android.systemui.shared.clocks.view.FlexClockView
import java.io.PrintWriter
import java.util.Locale
@@ -96,8 +100,8 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
largeClock.events.onZenDataChanged(data)
}
- override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
- val fontAxes = getDefaultAxes(clockCtx.settings).merge(axes).map { it.toSetting() }
+ override fun onFontAxesChanged(axes: ClockAxisStyle) {
+ val fontAxes = ClockAxisStyle(getDefaultAxes(clockCtx.settings).merge(axes))
smallClock.events.onFontAxesChanged(fontAxes)
largeClock.events.onFontAxesChanged(fontAxes)
}
@@ -107,11 +111,11 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
isDarkTheme: Boolean,
dozeFraction: Float,
foldFraction: Float,
- onBoundsChanged: (RectF) -> Unit,
+ clockListener: ClockEventListener?,
) {
events.onFontAxesChanged(clockCtx.settings.axes)
smallClock.run {
- layerController.onViewBoundsChanged = onBoundsChanged
+ layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) }
events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
@@ -119,7 +123,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
}
largeClock.run {
- layerController.onViewBoundsChanged = onBoundsChanged
+ layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) }
events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
@@ -162,66 +166,46 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
),
)
- private val LEGACY_FLEX_SETTINGS =
- listOf(
- GSFAxes.WEIGHT.toClockAxisSetting(600f),
- GSFAxes.WIDTH.toClockAxisSetting(100f),
- GSFAxes.ROUND.toClockAxisSetting(100f),
- GSFAxes.SLANT.toClockAxisSetting(0f),
- )
+ private val LEGACY_FLEX_SETTINGS = ClockAxisStyle {
+ put(GSFAxes.WEIGHT, 600f)
+ put(GSFAxes.WIDTH, 100f)
+ put(GSFAxes.ROUND, 100f)
+ put(GSFAxes.SLANT, 0f)
+ }
- val AXIS_PRESETS =
- listOf(
- FONT_AXES.map { it.toSetting() },
- LEGACY_FLEX_SETTINGS,
- listOf( // Porcelain
- GSFAxes.WEIGHT.toClockAxisSetting(500f),
- GSFAxes.WIDTH.toClockAxisSetting(100f),
- GSFAxes.ROUND.toClockAxisSetting(0f),
- GSFAxes.SLANT.toClockAxisSetting(0f),
- ),
- listOf( // Midnight
- GSFAxes.WEIGHT.toClockAxisSetting(300f),
- GSFAxes.WIDTH.toClockAxisSetting(100f),
- GSFAxes.ROUND.toClockAxisSetting(100f),
- GSFAxes.SLANT.toClockAxisSetting(-10f),
- ),
- listOf( // Sterling
- GSFAxes.WEIGHT.toClockAxisSetting(1000f),
- GSFAxes.WIDTH.toClockAxisSetting(100f),
- GSFAxes.ROUND.toClockAxisSetting(0f),
- GSFAxes.SLANT.toClockAxisSetting(0f),
- ),
- listOf( // Smoky Green
- GSFAxes.WEIGHT.toClockAxisSetting(150f),
- GSFAxes.WIDTH.toClockAxisSetting(50f),
- GSFAxes.ROUND.toClockAxisSetting(0f),
- GSFAxes.SLANT.toClockAxisSetting(0f),
- ),
- listOf( // Iris
- GSFAxes.WEIGHT.toClockAxisSetting(500f),
- GSFAxes.WIDTH.toClockAxisSetting(100f),
- GSFAxes.ROUND.toClockAxisSetting(100f),
- GSFAxes.SLANT.toClockAxisSetting(0f),
- ),
- listOf( // Margarita
- GSFAxes.WEIGHT.toClockAxisSetting(300f),
- GSFAxes.WIDTH.toClockAxisSetting(30f),
- GSFAxes.ROUND.toClockAxisSetting(100f),
- GSFAxes.SLANT.toClockAxisSetting(-10f),
- ),
- listOf( // Raspberry
- GSFAxes.WEIGHT.toClockAxisSetting(700f),
- GSFAxes.WIDTH.toClockAxisSetting(140f),
- GSFAxes.ROUND.toClockAxisSetting(100f),
- GSFAxes.SLANT.toClockAxisSetting(-7f),
- ),
- listOf( // Ultra Blue
- GSFAxes.WEIGHT.toClockAxisSetting(850f),
- GSFAxes.WIDTH.toClockAxisSetting(130f),
- GSFAxes.ROUND.toClockAxisSetting(0f),
- GSFAxes.SLANT.toClockAxisSetting(0f),
- ),
+ private val PRESET_COUNT = 8
+ private val PRESET_WIDTH_INIT = 30f
+ private val PRESET_WIDTH_STEP = 12.5f
+ private val PRESET_WEIGHT_INIT = 800f
+ private val PRESET_WEIGHT_STEP = -100f
+ private val BASE_PRESETS: List<ClockAxisStyle> = run {
+ val presets = mutableListOf<ClockAxisStyle>()
+ var weight = PRESET_WEIGHT_INIT
+ var width = PRESET_WIDTH_INIT
+ for (i in 1..PRESET_COUNT) {
+ presets.add(
+ ClockAxisStyle {
+ put(GSFAxes.WEIGHT, weight)
+ put(GSFAxes.WIDTH, width)
+ put(GSFAxes.ROUND, 0f)
+ put(GSFAxes.SLANT, 0f)
+ }
+ )
+
+ weight += PRESET_WEIGHT_STEP
+ width += PRESET_WIDTH_STEP
+ }
+
+ return@run presets
+ }
+
+ fun buildPresetGroup(resources: Resources, isRound: Boolean): AxisPresetConfig.Group {
+ val round = if (isRound) GSFAxes.ROUND.maxValue else GSFAxes.ROUND.minValue
+ return AxisPresetConfig.Group(
+ presets = BASE_PRESETS.map { it.copy { put(GSFAxes.ROUND, round) } },
+ // TODO(b/395647577): Placeholder Icon; Replace or remove
+ icon = resources.getDrawable(R.drawable.clock_default_thumbnail, null),
)
+ }
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 578a489c68c6..171a68f72e20 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -25,16 +25,18 @@ import com.android.systemui.animation.GSFAxes
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
import com.android.systemui.plugins.clocks.ClockFaceLayout
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.FontUtils.get
+import com.android.systemui.shared.clocks.FontUtils.set
import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff
import com.android.systemui.shared.clocks.view.FlexClockView
import com.android.systemui.shared.clocks.view.HorizontalAlignment
@@ -129,17 +131,10 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
layerController.faceEvents.onThemeChanged(theme)
}
- override fun onFontAxesChanged(settings: List<ClockFontAxisSetting>) {
- var axes = settings
- if (!isLargeClock) {
- axes =
- axes.map { axis ->
- if (axis.key == GSFAxes.WIDTH.tag && axis.value > SMALL_CLOCK_MAX_WDTH) {
- axis.copy(value = SMALL_CLOCK_MAX_WDTH)
- } else {
- axis
- }
- }
+ override fun onFontAxesChanged(settings: ClockAxisStyle) {
+ var axes = ClockAxisStyle(settings)
+ if (!isLargeClock && axes[GSFAxes.WIDTH] > SMALL_CLOCK_MAX_WDTH) {
+ axes[GSFAxes.WIDTH] = SMALL_CLOCK_MAX_WDTH
}
layerController.events.onFontAxesChanged(axes)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt
index 212b1e29d1b8..722d76beedbb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt
@@ -18,26 +18,36 @@ package com.android.systemui.shared.clocks
import com.android.systemui.animation.AxisDefinition
import com.android.systemui.plugins.clocks.AxisType
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockFontAxis
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
-fun AxisDefinition.toClockAxis(
- type: AxisType,
- currentValue: Float? = null,
- name: String,
- description: String,
-): ClockFontAxis {
- return ClockFontAxis(
- key = this.tag,
- type = type,
- maxValue = this.maxValue,
- minValue = this.minValue,
- currentValue = currentValue ?: this.defaultValue,
- name = name,
- description = description,
- )
-}
+object FontUtils {
+ fun AxisDefinition.toClockAxis(
+ type: AxisType,
+ currentValue: Float? = null,
+ name: String,
+ description: String,
+ ): ClockFontAxis {
+ return ClockFontAxis(
+ key = this.tag,
+ type = type,
+ maxValue = this.maxValue,
+ minValue = this.minValue,
+ currentValue = currentValue ?: this.defaultValue,
+ name = name,
+ description = description,
+ )
+ }
+
+ fun ClockAxisStyle.put(def: AxisDefinition, value: Float? = null) {
+ this.put(def.tag, value ?: def.defaultValue)
+ }
+
+ operator fun ClockAxisStyle.set(def: AxisDefinition, value: Float) {
+ this[def.tag] = value
+ }
-fun AxisDefinition.toClockAxisSetting(value: Float? = null): ClockFontAxisSetting {
- return ClockFontAxisSetting(this.tag, value ?: this.defaultValue)
+ operator fun ClockAxisStyle.get(def: AxisDefinition): Float {
+ return this[def.tag] ?: def.defaultValue
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
index 336c66eed889..9ac9e60f05fd 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
@@ -16,13 +16,13 @@
package com.android.systemui.shared.clocks
-import android.graphics.RectF
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.VRectF
interface SimpleClockLayerController {
val view: View
@@ -32,5 +32,5 @@ interface SimpleClockLayerController {
val config: ClockFaceConfig
@VisibleForTesting var fakeTimeMills: Long?
- var onViewBoundsChanged: ((RectF) -> Unit)?
+ var onViewBoundsChanged: ((VRectF) -> Unit)?
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index 97004ef6f9a9..7be9a936cbd3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -26,10 +26,10 @@ import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -172,7 +172,7 @@ open class SimpleDigitalHandLayerController(
override fun onZenDataChanged(data: ZenData) {}
- override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ override fun onFontAxesChanged(axes: ClockAxisStyle) {
view.updateAxes(axes)
}
}
@@ -229,15 +229,7 @@ open class SimpleDigitalHandLayerController(
}
override fun onThemeChanged(theme: ThemeConfig) {
- val color =
- when {
- theme.seedColor != null -> theme.seedColor!!
- theme.isDarkTheme ->
- clockCtx.resources.getColor(android.R.color.system_accent1_100)
- else -> clockCtx.resources.getColor(android.R.color.system_accent2_600)
- }
-
- view.updateColor(color)
+ view.updateColor(theme.getDefaultColor(clockCtx.context))
refreshTime()
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt
index 1e90a2370786..0740b0e504cb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt
@@ -18,8 +18,9 @@ package com.android.systemui.shared.clocks
import android.graphics.Rect
import android.view.View
-import com.android.systemui.shared.clocks.VPoint.Companion.center
-import com.android.systemui.shared.clocks.VPointF.Companion.center
+import com.android.systemui.plugins.clocks.VPoint.Companion.center
+import com.android.systemui.plugins.clocks.VPointF
+import com.android.systemui.plugins.clocks.VPointF.Companion.center
object ViewUtils {
fun View.computeLayoutDiff(targetRegion: Rect, isLargeClock: Boolean): VPointF {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index c765ea9cc84c..4531aed0e83d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shared.clocks.view
import android.graphics.Canvas
-import android.graphics.RectF
import android.icu.text.NumberFormat
import android.util.MathUtils.constrainedMap
import android.view.View
@@ -27,15 +26,17 @@ import androidx.annotation.VisibleForTesting
import androidx.core.view.children
import com.android.app.animation.Interpolators
import com.android.systemui.customization.R
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockLogger
+import com.android.systemui.plugins.clocks.VPoint
+import com.android.systemui.plugins.clocks.VPointF
+import com.android.systemui.plugins.clocks.VPointF.Companion.max
+import com.android.systemui.plugins.clocks.VPointF.Companion.times
+import com.android.systemui.plugins.clocks.VRectF
import com.android.systemui.shared.clocks.CanvasUtil.translate
import com.android.systemui.shared.clocks.CanvasUtil.use
import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
-import com.android.systemui.shared.clocks.VPointF
-import com.android.systemui.shared.clocks.VPointF.Companion.max
-import com.android.systemui.shared.clocks.VPointF.Companion.times
import com.android.systemui.shared.clocks.ViewUtils.measuredSize
import java.util.Locale
import kotlin.collections.filterNotNull
@@ -100,7 +101,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
updateLocale(Locale.getDefault())
}
- var onViewBoundsChanged: ((RectF) -> Unit)? = null
+ var onViewBoundsChanged: ((VRectF) -> Unit)? = null
private val digitOffsets = mutableMapOf<Int, Float>()
protected fun calculateSize(
@@ -109,13 +110,11 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
shouldMeasureChildren: Boolean,
): VPointF {
maxChildSize = VPointF(-1, -1)
- fun SimpleDigitalClockTextView.getSize() = VPointF(measuredWidth, measuredHeight)
-
childViews.forEach { textView ->
if (shouldMeasureChildren) {
textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
}
- maxChildSize = max(maxChildSize, textView.getSize())
+ maxChildSize = max(maxChildSize, textView.measuredSize)
}
aodTranslate = VPointF.ZERO
// TODO(b/364680879): Cleanup
@@ -180,8 +179,8 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
)
private fun updateMeasuredSize(
- widthMeasureSpec: Int = measuredWidthAndState,
- heightMeasureSpec: Int = measuredHeightAndState,
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int,
shouldMeasureChildren: Boolean,
) {
val size = calculateSize(widthMeasureSpec, heightMeasureSpec, shouldMeasureChildren)
@@ -190,13 +189,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
fun updateLocation() {
val layoutBounds = this.layoutBounds ?: return
- val bounds =
- RectF(
- layoutBounds.centerX() - measuredWidth / 2f,
- layoutBounds.centerY() - measuredHeight / 2f,
- layoutBounds.centerX() + measuredWidth / 2f,
- layoutBounds.centerY() + measuredHeight / 2f,
- )
+ val bounds = VRectF.fromCenter(layoutBounds.center, this.measuredSize)
setFrame(
bounds.left.roundToInt(),
bounds.top.roundToInt(),
@@ -216,16 +209,11 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
onAnimateDoze = null
}
- private val layoutBounds = RectF()
+ private var layoutBounds = VRectF.ZERO
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
logger.onLayout(changed, left, top, right, bottom)
-
- layoutBounds.left = left.toFloat()
- layoutBounds.top = top.toFloat()
- layoutBounds.right = right.toFloat()
- layoutBounds.bottom = bottom.toFloat()
-
+ layoutBounds = VRectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
updateChildFrames(isLayout = true)
}
@@ -284,7 +272,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
invalidate()
}
- fun updateAxes(axes: List<ClockFontAxisSetting>) {
+ fun updateAxes(axes: ClockAxisStyle) {
childViews.forEach { view -> view.updateAxes(axes) }
requestLayout()
}
@@ -357,7 +345,18 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
}
fun animateFidget(x: Float, y: Float) {
- childViews.forEach { view -> view.animateFidget(x, y) }
+ val touchPt = VPointF(x, y)
+ val ints = intArrayOf(0, 0)
+ childViews
+ .sortedBy { view ->
+ view.getLocationInWindow(ints)
+ val loc = VPoint(ints[0], ints[1])
+ val center = loc + view.measuredSize / 2f
+ (center - touchPt).length()
+ }
+ .forEachIndexed { i, view ->
+ view.animateFidget(FIDGET_DELAYS[min(i, FIDGET_DELAYS.size - 1)])
+ }
}
private fun updateLocale(locale: Locale) {
@@ -441,6 +440,8 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
val AOD_HORIZONTAL_TRANSLATE_RATIO = -0.15F
val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F
+ val FIDGET_DELAYS = listOf(0L, 75L, 150L, 225L)
+
// 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
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index abe5cc27f6d7..377a24c2899b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -23,9 +23,7 @@ import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
-import android.graphics.RectF
import android.os.VibrationEffect
-import android.text.Layout
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
@@ -38,13 +36,18 @@ import android.view.animation.PathInterpolator
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.AxisDefinition
import com.android.systemui.animation.GSFAxes
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.animation.TextAnimatorListener
import com.android.systemui.customization.R
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.replace
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.toFVar
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockLogger
+import com.android.systemui.plugins.clocks.VPoint
+import com.android.systemui.plugins.clocks.VPointF
+import com.android.systemui.plugins.clocks.VPointF.Companion.size
+import com.android.systemui.plugins.clocks.VRectF
+import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.shared.clocks.CanvasUtil.translate
import com.android.systemui.shared.clocks.CanvasUtil.use
import com.android.systemui.shared.clocks.ClockContext
@@ -52,12 +55,9 @@ import com.android.systemui.shared.clocks.DigitTranslateAnimator
import com.android.systemui.shared.clocks.DimensionParser
import com.android.systemui.shared.clocks.FLEX_CLOCK_ID
import com.android.systemui.shared.clocks.FontTextStyle
-import com.android.systemui.shared.clocks.VPoint
-import com.android.systemui.shared.clocks.VPointF
-import com.android.systemui.shared.clocks.VPointF.Companion.size
+import com.android.systemui.shared.clocks.FontUtils.set
import com.android.systemui.shared.clocks.ViewUtils.measuredSize
import com.android.systemui.shared.clocks.ViewUtils.size
-import com.android.systemui.shared.clocks.toClockAxisSetting
import java.lang.Thread
import kotlin.math.max
import kotlin.math.min
@@ -65,11 +65,11 @@ import kotlin.math.roundToInt
private val TAG = SimpleDigitalClockTextView::class.simpleName!!
-private fun Paint.getTextBounds(text: CharSequence, result: RectF = RectF()): RectF {
- val rect = Rect()
- this.getTextBounds(text, 0, text.length, rect)
- result.set(rect)
- return result
+private val tempRect = Rect()
+
+private fun Paint.getTextBounds(text: CharSequence): VRectF {
+ this.getTextBounds(text, 0, text.length, tempRect)
+ return VRectF(tempRect)
}
enum class VerticalAlignment {
@@ -122,9 +122,9 @@ open class SimpleDigitalClockTextView(
private val isLegacyFlex = clockCtx.settings.clockId == FLEX_CLOCK_ID
private val fixedAodAxes =
when {
- !isLegacyFlex -> listOf(AOD_WEIGHT_AXIS, WIDTH_AXIS)
- isLargeClock -> listOf(FLEX_AOD_LARGE_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS)
- else -> listOf(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS)
+ !isLegacyFlex -> fromAxes(AOD_WEIGHT_AXIS, WIDTH_AXIS)
+ isLargeClock -> fromAxes(FLEX_AOD_LARGE_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS)
+ else -> fromAxes(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS)
}
private var lsFontVariation: String
@@ -134,15 +134,15 @@ open class SimpleDigitalClockTextView(
init {
val roundAxis = if (!isLegacyFlex) ROUND_AXIS else FLEX_ROUND_AXIS
val lsFontAxes =
- if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS)
- else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS)
+ if (!isLegacyFlex) fromAxes(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS)
+ else fromAxes(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS)
lsFontVariation = lsFontAxes.toFVar()
- aodFontVariation = (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar()
+ aodFontVariation = fixedAodAxes.copyWith(fromAxes(roundAxis, SLANT_AXIS)).toFVar()
fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar()
}
- var onViewBoundsChanged: ((RectF) -> Unit)? = null
+ var onViewBoundsChanged: ((VRectF) -> Unit)? = null
private val parser = DimensionParser(clockCtx.context)
var maxSingleDigitHeight = -1f
var maxSingleDigitWidth = -1f
@@ -158,13 +158,13 @@ open class SimpleDigitalClockTextView(
private val initThread = Thread.currentThread()
// textBounds is the size of text in LS, which only measures current text in lockscreen style
- var textBounds = RectF()
+ var textBounds = VRectF.ZERO
// prevTextBounds and targetTextBounds are to deal with dozing animation between LS and AOD
// especially for the textView which has different bounds during the animation
// prevTextBounds holds the state we are transitioning from
- private val prevTextBounds = RectF()
+ private var prevTextBounds = VRectF.ZERO
// targetTextBounds holds the state we are interpolating to
- private val targetTextBounds = RectF()
+ private var targetTextBounds = VRectF.ZERO
protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: ClockLogger.INIT_LOGGER
@@ -174,11 +174,6 @@ open class SimpleDigitalClockTextView(
private val typefaceCache = clockCtx.typefaceCache.getVariantCache("")
- @VisibleForTesting
- var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
- TextAnimator(layout, typefaceCache, invalidateCb)
- }
-
var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER
@@ -205,17 +200,17 @@ open class SimpleDigitalClockTextView(
invalidate()
}
- fun updateAxes(lsAxes: List<ClockFontAxisSetting>) {
+ fun updateAxes(lsAxes: ClockAxisStyle) {
lsFontVariation = lsAxes.toFVar()
- aodFontVariation = lsAxes.replace(fixedAodAxes).toFVar()
+ aodFontVariation = lsAxes.copyWith(fixedAodAxes).toFVar()
fidgetFontVariation = buildFidgetVariation(lsAxes).toFVar()
logger.updateAxes(lsFontVariation, aodFontVariation)
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
typeface = lockScreenPaint.typeface
- lockScreenPaint.getTextBounds(text, textBounds)
- targetTextBounds.set(textBounds)
+ textBounds = lockScreenPaint.getTextBounds(text)
+ targetTextBounds = textBounds
textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation))
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
@@ -224,19 +219,16 @@ open class SimpleDigitalClockTextView(
invalidate()
}
- fun buildFidgetVariation(axes: List<ClockFontAxisSetting>): List<ClockFontAxisSetting> {
- val result = mutableListOf<ClockFontAxisSetting>()
- for (axis in axes) {
- result.add(
- FIDGET_DISTS.get(axis.key)?.let { (dist, midpoint) ->
- ClockFontAxisSetting(
- axis.key,
- axis.value + dist * if (axis.value > midpoint) -1 else 1,
- )
- } ?: axis
- )
- }
- return result
+ fun buildFidgetVariation(axes: ClockAxisStyle): ClockAxisStyle {
+ return ClockAxisStyle(
+ axes.items
+ .map { (key, value) ->
+ FIDGET_DISTS.get(key)?.let { (dist, midpoint) ->
+ key to value + dist * if (value > midpoint) -1 else 1
+ } ?: (key to value)
+ }
+ .toMap()
+ )
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -246,7 +238,18 @@ open class SimpleDigitalClockTextView(
val layout = this.layout
if (layout != null) {
if (!this::textAnimator.isInitialized) {
- textAnimator = textAnimatorFactory(layout, ::invalidate)
+ textAnimator =
+ TextAnimator(
+ layout,
+ typefaceCache,
+ object : TextAnimatorListener {
+ override fun onInvalidate() = invalidate()
+
+ override fun onRebased() = updateTextBounds()
+
+ override fun onPaintModified() = updateTextBounds()
+ },
+ )
setInterpolatorPaint()
} else {
textAnimator.updateLayout(layout)
@@ -271,7 +274,7 @@ open class SimpleDigitalClockTextView(
override fun onDraw(canvas: Canvas) {
logger.onDraw(textAnimator.textInterpolator.shapedText)
- val interpProgress = getInterpolatedProgress()
+ val interpProgress = textAnimator.progress
val interpBounds = getInterpolatedTextBounds(interpProgress)
if (interpProgress != drawnProgress) {
drawnProgress = interpProgress
@@ -286,7 +289,7 @@ open class SimpleDigitalClockTextView(
canvas.use {
digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
canvas.translate(getDrawTranslation(interpBounds))
- if (isLayoutRtl()) canvas.translate(interpBounds.width() - textBounds.width(), 0f)
+ if (isLayoutRtl()) canvas.translate(interpBounds.width - textBounds.width, 0f)
textAnimator.draw(canvas)
}
}
@@ -301,16 +304,12 @@ open class SimpleDigitalClockTextView(
super.setAlpha(alpha)
}
- private val layoutBounds = RectF()
+ private var layoutBounds = VRectF.ZERO
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
logger.onLayout(changed, left, top, right, bottom)
-
- layoutBounds.left = left.toFloat()
- layoutBounds.top = top.toFloat()
- layoutBounds.right = right.toFloat()
- layoutBounds.bottom = bottom.toFloat()
+ layoutBounds = VRectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
}
override fun invalidate() {
@@ -326,11 +325,11 @@ open class SimpleDigitalClockTextView(
fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
if (!this::textAnimator.isInitialized) return
- logger.animateDoze()
+ logger.animateDoze(isDozing, isAnimated)
textAnimator.setTextStyle(
TextAnimator.Style(
fVar = if (isDozing) aodFontVariation else lsFontVariation,
- color = if (isDozing) AOD_COLOR else lockscreenColor,
+ color = if (isDozing && !ambientAod()) AOD_COLOR else lockscreenColor,
textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
),
TextAnimator.Animation(
@@ -339,7 +338,11 @@ open class SimpleDigitalClockTextView(
interpolator = aodDozingInterpolator,
),
)
- updateTextBoundsForTextAnimator()
+
+ if (!isAnimated) {
+ requestLayout()
+ (parent as? FlexClockView)?.requestLayout()
+ }
}
fun animateCharge() {
@@ -365,14 +368,14 @@ open class SimpleDigitalClockTextView(
duration = CHARGE_ANIMATION_DURATION,
),
)
- updateTextBoundsForTextAnimator()
},
),
)
- updateTextBoundsForTextAnimator()
}
- fun animateFidget(x: Float, y: Float) {
+ fun animateFidget(x: Float, y: Float) = animateFidget(0L)
+
+ fun animateFidget(delay: Long) {
if (!this::textAnimator.isInitialized || textAnimator.isRunning) {
// Skip fidget animation if other animation is already playing.
return
@@ -381,13 +384,13 @@ open class SimpleDigitalClockTextView(
logger.animateFidget(x, y)
clockCtx.vibrator?.vibrate(FIDGET_HAPTICS)
- // TODO(b/374306512): Delay each glyph's animation based on x/y position
textAnimator.setTextStyle(
TextAnimator.Style(fVar = fidgetFontVariation),
TextAnimator.Animation(
animate = isAnimationEnabled,
duration = FIDGET_ANIMATION_DURATION,
interpolator = FIDGET_INTERPOLATOR,
+ startDelay = delay,
onAnimationEnd = {
textAnimator.setTextStyle(
TextAnimator.Style(fVar = lsFontVariation),
@@ -397,18 +400,16 @@ open class SimpleDigitalClockTextView(
interpolator = FIDGET_INTERPOLATOR,
),
)
- updateTextBoundsForTextAnimator()
},
),
)
- updateTextBoundsForTextAnimator()
}
fun refreshText() {
- lockScreenPaint.getTextBounds(text, textBounds)
- if (this::textAnimator.isInitialized) {
- textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds)
- }
+ textBounds = lockScreenPaint.getTextBounds(text)
+ targetTextBounds =
+ if (!this::textAnimator.isInitialized) textBounds
+ else textAnimator.textInterpolator.targetPaint.getTextBounds(text)
if (layout == null) {
requestLayout()
@@ -424,26 +425,24 @@ open class SimpleDigitalClockTextView(
id == R.id.MINUTE_SECOND_DIGIT
}
- private fun getInterpolatedProgress(): Float {
- return textAnimator.animator?.let { it.animatedValue as Float } ?: 1f
- }
-
/** Returns the interpolated text bounding rect based on interpolation progress */
- private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): RectF {
- if (!textAnimator.isRunning || progress >= 1f) {
- return RectF(targetTextBounds)
+ private fun getInterpolatedTextBounds(progress: Float = textAnimator.progress): VRectF {
+ if (progress <= 0f) {
+ return prevTextBounds
+ } else if (!textAnimator.isRunning || progress >= 1f) {
+ return targetTextBounds
}
- return RectF().apply {
- left = lerp(prevTextBounds.left, targetTextBounds.left, progress)
- right = lerp(prevTextBounds.right, targetTextBounds.right, progress)
- top = lerp(prevTextBounds.top, targetTextBounds.top, progress)
- bottom = lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress)
- }
+ return VRectF(
+ left = lerp(prevTextBounds.left, targetTextBounds.left, progress),
+ right = lerp(prevTextBounds.right, targetTextBounds.right, progress),
+ top = lerp(prevTextBounds.top, targetTextBounds.top, progress),
+ bottom = lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress),
+ )
}
private fun computeMeasuredSize(
- interpBounds: RectF,
+ interpBounds: VRectF,
widthMeasureSpec: Int = measuredWidthAndState,
heightMeasureSpec: Int = measuredHeightAndState,
): VPointF {
@@ -456,11 +455,11 @@ open class SimpleDigitalClockTextView(
return VPointF(
when {
mode.x == EXACTLY -> MeasureSpec.getSize(widthMeasureSpec).toFloat()
- else -> interpBounds.width() + 2 * lockScreenPaint.strokeWidth
+ else -> interpBounds.width + 2 * lockScreenPaint.strokeWidth
},
when {
mode.y == EXACTLY -> MeasureSpec.getSize(heightMeasureSpec).toFloat()
- else -> interpBounds.height() + 2 * lockScreenPaint.strokeWidth
+ else -> interpBounds.height + 2 * lockScreenPaint.strokeWidth
},
)
}
@@ -481,47 +480,35 @@ open class SimpleDigitalClockTextView(
MeasureSpec.makeMeasureSpec(measureBounds.x.roundToInt(), mode.x),
MeasureSpec.makeMeasureSpec(measureBounds.y.roundToInt(), mode.y),
)
+
+ logger.d({
+ val size = VPointF.fromLong(long1)
+ val mode = VPoint.fromLong(long2)
+ "setInterpolatedSize(size=$size, mode=$mode)"
+ }) {
+ long1 = measureBounds.toLong()
+ long2 = mode.toLong()
+ }
}
/** Set the location of the view to match the interpolated text bounds */
- private fun setInterpolatedLocation(measureSize: VPointF): RectF {
- val targetRect = RectF()
- targetRect.apply {
- when (xAlignment) {
- XAlignment.LEFT -> {
- left = layoutBounds.left
- right = layoutBounds.left + measureSize.x
- }
- XAlignment.CENTER -> {
- left = layoutBounds.centerX() - measureSize.x / 2f
- right = layoutBounds.centerX() + measureSize.x / 2f
- }
- XAlignment.RIGHT -> {
- left = layoutBounds.right - measureSize.x
- right = layoutBounds.right
- }
- }
-
- when (verticalAlignment) {
- VerticalAlignment.TOP -> {
- top = layoutBounds.top
- bottom = layoutBounds.top + measureSize.y
- }
- VerticalAlignment.CENTER -> {
- top = layoutBounds.centerY() - measureSize.y / 2f
- bottom = layoutBounds.centerY() + measureSize.y / 2f
- }
- VerticalAlignment.BOTTOM -> {
- top = layoutBounds.bottom - measureSize.y
- bottom = layoutBounds.bottom
- }
- VerticalAlignment.BASELINE -> {
- top = layoutBounds.centerY() - measureSize.y / 2f
- bottom = layoutBounds.centerY() + measureSize.y / 2f
- }
- }
- }
+ private fun setInterpolatedLocation(measureSize: VPointF): VRectF {
+ val pos =
+ VPointF(
+ when (xAlignment) {
+ XAlignment.LEFT -> layoutBounds.left
+ XAlignment.CENTER -> layoutBounds.center.x - measureSize.x / 2f
+ XAlignment.RIGHT -> layoutBounds.right - measureSize.x
+ },
+ when (verticalAlignment) {
+ VerticalAlignment.TOP -> layoutBounds.top
+ VerticalAlignment.CENTER -> layoutBounds.center.y - measureSize.y / 2f
+ VerticalAlignment.BOTTOM -> layoutBounds.bottom - measureSize.y
+ VerticalAlignment.BASELINE -> layoutBounds.center.y - measureSize.y / 2f
+ },
+ )
+ val targetRect = VRectF.fromTopLeft(pos, measureSize)
setFrame(
targetRect.left.roundToInt(),
targetRect.top.roundToInt(),
@@ -529,10 +516,13 @@ open class SimpleDigitalClockTextView(
targetRect.bottom.roundToInt(),
)
onViewBoundsChanged?.let { it(targetRect) }
+ logger.d({ "setInterpolatedLocation(${VRectF.fromLong(long1)})" }) {
+ long1 = targetRect.toLong()
+ }
return targetRect
}
- private fun getDrawTranslation(interpBounds: RectF): VPointF {
+ private fun getDrawTranslation(interpBounds: VRectF): VPointF {
val sizeDiff = this.measuredSize - interpBounds.size
val alignment =
VPointF(
@@ -581,11 +571,11 @@ open class SimpleDigitalClockTextView(
if (fontSizePx > 0) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
lockScreenPaint.textSize = textSize
- lockScreenPaint.getTextBounds(text, textBounds)
- targetTextBounds.set(textBounds)
+ textBounds = lockScreenPaint.getTextBounds(text)
+ targetTextBounds = textBounds
}
if (!constrainedByHeight) {
- val lastUnconstrainedHeight = textBounds.height() + lockScreenPaint.strokeWidth * 2
+ val lastUnconstrainedHeight = textBounds.height + lockScreenPaint.strokeWidth * 2
fontSizeAdjustFactor = lastUnconstrainedHeight / lastUnconstrainedTextSize
}
@@ -603,8 +593,8 @@ open class SimpleDigitalClockTextView(
for (i in 0..9) {
val rectForCalculate = lockScreenPaint.getTextBounds("$i")
- maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height())
- maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width())
+ maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height)
+ maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width)
}
maxSingleDigitWidth += 2 * lockScreenPaint.strokeWidth
maxSingleDigitHeight += 2 * lockScreenPaint.strokeWidth
@@ -631,9 +621,10 @@ open class SimpleDigitalClockTextView(
* rebase if previous animator is canceled so basePaint will store the state we transition from
* and targetPaint will store the state we transition to
*/
- private fun updateTextBoundsForTextAnimator() {
- textAnimator.textInterpolator.basePaint.getTextBounds(text, prevTextBounds)
- textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds)
+ private fun updateTextBounds() {
+ drawnProgress = null
+ prevTextBounds = textAnimator.textInterpolator.basePaint.getTextBounds(text)
+ targetTextBounds = textAnimator.textInterpolator.targetPaint.getTextBounds(text)
}
/**
@@ -671,18 +662,22 @@ open class SimpleDigitalClockTextView(
)
val AOD_COLOR = Color.WHITE
- val LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(400f)
- val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(200f)
- val WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(85f)
- val ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(0f)
- val SLANT_AXIS = GSFAxes.SLANT.toClockAxisSetting(0f)
+ private val LS_WEIGHT_AXIS = GSFAxes.WEIGHT to 400f
+ private val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT to 200f
+ private val WIDTH_AXIS = GSFAxes.WIDTH to 85f
+ private val ROUND_AXIS = GSFAxes.ROUND to 0f
+ private val SLANT_AXIS = GSFAxes.SLANT to 0f
// Axes for Legacy version of the Flex Clock
- val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(600f)
- val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(74f)
- val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(133f)
- val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(100f)
- val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(43f)
- val FLEX_ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(100f)
+ private val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT to 600f
+ private val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT to 74f
+ private val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT to 133f
+ private val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH to 100f
+ private val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH to 43f
+ private val FLEX_ROUND_AXIS = GSFAxes.ROUND to 100f
+
+ private fun fromAxes(vararg axes: Pair<AxisDefinition, Float>): ClockAxisStyle {
+ return ClockAxisStyle(axes.map { (def, value) -> def.tag to value }.toMap())
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index e26e19d27417..29647cd082b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -16,12 +16,18 @@
package com.android.keyguard;
+import static com.android.internal.widget.flags.Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import android.hardware.input.InputManager;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import android.view.ViewGroup;
@@ -90,6 +96,8 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
@Mock
private UserActivityNotifier mUserActivityNotifier;
private NumPadKey[] mButtons = new NumPadKey[]{};
+ @Mock
+ private InputManager mInputManager;
private KeyguardPinBasedInputViewController mKeyguardPinViewController;
@@ -118,12 +126,13 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
new KeyguardKeyboardInteractor(new FakeKeyboardRepository());
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker,
mEmergencyButtonController, mFalsingCollector, featureFlags,
mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer,
- mUserActivityNotifier) {
+ mUserActivityNotifier, mInputManager) {
@Override
public void onResume(int reason) {
super.onResume(reason);
@@ -148,4 +157,112 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mKeyguardPinViewController.resetState();
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin);
}
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_addDevice_notKeyboard() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ mKeyguardPinViewController.onViewAttached();
+ mKeyguardPinViewController.onInputDeviceAdded(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_addDevice_keyboard() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ mKeyguardPinViewController.onViewAttached();
+ mKeyguardPinViewController.onInputDeviceAdded(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_addDevice_multipleKeyboards() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ mKeyguardPinViewController.onViewAttached();
+ mKeyguardPinViewController.onInputDeviceAdded(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ mKeyguardPinViewController.onInputDeviceAdded(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_removeDevice_notKeyboard() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ mKeyguardPinViewController.onViewAttached();
+ mKeyguardPinViewController.onInputDeviceRemoved(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_removeDevice_keyboard() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, false);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(0)).setShowPassword(false);
+ mKeyguardPinViewController.onViewAttached();
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ mKeyguardPinViewController.onInputDeviceRemoved(1);
+ verify(mPasswordEntry, times(2)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_removeDevice_multipleKeyboards() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, true);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(0)).setShowPassword(false);
+ mKeyguardPinViewController.onViewAttached();
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ mKeyguardPinViewController.onInputDeviceRemoved(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_updateDevice_notKeyboard() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ mKeyguardPinViewController.onViewAttached();
+ mKeyguardPinViewController.onInputDeviceChanged(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_updateDevice_keyboard() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, false);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(0)).setShowPassword(false);
+ mKeyguardPinViewController.onViewAttached();
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ mKeyguardPinViewController.onInputDeviceChanged(1);
+ verify(mPasswordEntry, times(2)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void updateAnimations_updateDevice_multipleKeyboards() {
+ when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, true);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(0)).setShowPassword(false);
+ mKeyguardPinViewController.onViewAttached();
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ mKeyguardPinViewController.onInputDeviceChanged(1);
+ verify(mPasswordEntry, times(1)).setShowPassword(true);
+ verify(mPasswordEntry, times(1)).setShowPassword(false);
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 142a2868ec14..7fea06ec7f41 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.keyguard
+import android.hardware.input.InputManager
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
@@ -104,6 +105,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Mock lateinit var enterButton: View
@Mock lateinit var uiEventLogger: UiEventLogger
@Mock lateinit var mUserActivityNotifier: UserActivityNotifier
+ @Mock lateinit var inputManager: InputManager
@Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@@ -154,6 +156,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
keyguardKeyboardInteractor,
kosmos.bouncerHapticPlayer,
mUserActivityNotifier,
+ inputManager,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index c751a7db51dc..003669da498e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.keyguard
+import android.hardware.input.InputManager
import android.telephony.TelephonyManager
import android.testing.TestableLooper
import android.view.LayoutInflater
@@ -73,6 +74,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
private val updateMonitorCallbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ @Mock private lateinit var inputManager: InputManager
private val kosmos = testKosmos()
@@ -107,6 +109,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
keyguardKeyboardInteractor,
kosmos.bouncerHapticPlayer,
mUserActivityNotifier,
+ inputManager,
)
underTest.init()
underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index c34682551eda..85cb388ace5c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.keyguard
+import android.hardware.input.InputManager
import android.telephony.PinResult
import android.telephony.TelephonyManager
import android.testing.TestableLooper
@@ -65,6 +66,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
private lateinit var keyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
+ @Mock private lateinit var inputManager: InputManager
private val kosmos = testKosmos()
@@ -102,6 +104,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
keyguardKeyboardInteractor,
kosmos.bouncerHapticPlayer,
mUserActivityNotifier,
+ inputManager,
)
underTest.init()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
index 7fb879c02778..f0c9141a76e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
@@ -32,7 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt
new file mode 100644
index 000000000000..4daf02332d41
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kairos.toColdConflatedFlow
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KairosCoreStartableTest : SysuiTestCase() {
+
+ @Test
+ fun kairosNetwork_usedBeforeStarted() =
+ testKosmos().useUnconfinedTestDispatcher().runKairosTest {
+ lateinit var activatable: TestActivatable
+ val underTest = KairosCoreStartable(applicationCoroutineScope) { setOf(activatable) }
+ activatable = TestActivatable(underTest)
+
+ // collect from the cold flow before starting the CoreStartable
+ var collectCount = 0
+ testScope.backgroundScope.launch { activatable.coldFlow.collect { collectCount++ } }
+
+ // start the CoreStartable
+ underTest.start()
+
+ // verify emissions are received
+ activatable.emitEvent()
+
+ assertThat(collectCount).isEqualTo(1)
+ }
+
+ private class TestActivatable(network: KairosNetwork) : KairosBuilder by kairosBuilder() {
+ private val emitter = MutableSharedFlow<Unit>()
+ private val events = buildEvents { emitter.toEvents() }
+
+ val coldFlow = events.toColdConflatedFlow(network)
+
+ suspend fun emitEvent() = emitter.emit(Unit)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 0b13900d826b..d118ace08b85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -71,6 +71,7 @@ import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.shared.QSSettingsPackageRepository;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -108,6 +109,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
private static final String TEST_LABEL = "label";
private static final int TEST_PRESET_INDEX = 1;
private static final String TEST_PRESET_NAME = "test_preset";
+ private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Mock
@@ -137,6 +139,12 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Mock
private HearingDevicesUiEventLogger mUiEventLogger;
@Mock
+ private QSSettingsPackageRepository mQSSettingsPackageRepository;
+ @Mock
+ private HearingDevicesInputRoutingController.Factory mInputRoutingFactory;
+ @Mock
+ private HearingDevicesInputRoutingController mInputRoutingController;
+ @Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothDevice mDevice;
@@ -164,6 +172,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice));
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
+ when(mQSSettingsPackageRepository.getSettingsPackageName())
+ .thenReturn(SETTINGS_PACKAGE_NAME);
when(mDevice.getBondState()).thenReturn(BOND_BONDED);
when(mDevice.isConnected()).thenReturn(true);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
@@ -178,6 +188,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
+ when(mInputRoutingFactory.create(any())).thenReturn(mInputRoutingController);
mContext.setMockPackageManager(mPackageManager);
}
@@ -195,6 +206,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
anyInt(), any());
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
+ assertThat(intentCaptor.getValue().getPackage()).isEqualTo(SETTINGS_PACKAGE_NAME);
verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR,
TEST_LAUNCH_SOURCE_ID);
}
@@ -210,6 +222,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
anyInt(), any());
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS);
+ assertThat(intentCaptor.getValue().getPackage()).isEqualTo(SETTINGS_PACKAGE_NAME);
verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK,
TEST_LAUNCH_SOURCE_ID);
}
@@ -341,6 +354,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mExecutor.runAllReady();
ViewGroup ambientLayout = getAmbientLayout(mDialog);
assertThat(ambientLayout.getVisibility()).isEqualTo(View.VISIBLE);
@@ -392,7 +406,9 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mExecutor,
mExecutor,
mAudioManager,
- mUiEventLogger
+ mUiEventLogger,
+ mQSSettingsPackageRepository,
+ mInputRoutingFactory
);
mDialog = mDialogDelegate.createDialog();
}
@@ -429,7 +445,6 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
return dialog.requireViewById(R.id.ambient_layout);
}
-
private int countChildWithoutSpace(ViewGroup viewGroup) {
int spaceCount = 0;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt
new file mode 100644
index 000000000000..0a41b771a335
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingControllerTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.hearingaid
+
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.HapClientProfile
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.hearingaid.HearingDevicesInputRoutingController.InputRoutingControlAvailableCallback
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HearingDevicesInputRoutingControllerTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private var hapClientProfile: HapClientProfile = mock()
+ private var cachedDevice: CachedBluetoothDevice = mock()
+ private var memberCachedDevice: CachedBluetoothDevice = mock()
+ private var btDevice: android.bluetooth.BluetoothDevice = mock()
+ private var audioManager: AudioManager = mock()
+ private lateinit var underTest: HearingDevicesInputRoutingController
+ private val testDispatcher = kosmos.testDispatcher
+
+ @Before
+ fun setUp() {
+ hapClientProfile.stub { on { isProfileReady } doReturn true }
+ cachedDevice.stub {
+ on { device } doReturn btDevice
+ on { profiles } doReturn listOf(hapClientProfile)
+ }
+ memberCachedDevice.stub {
+ on { device } doReturn btDevice
+ on { profiles } doReturn listOf(hapClientProfile)
+ }
+
+ underTest = HearingDevicesInputRoutingController(mContext, audioManager, testDispatcher)
+ underTest.setDevice(cachedDevice)
+ }
+
+ @Test
+ fun isInputRoutingControlAvailable_validInput_supportHapProfile_returnTrue() {
+ testScope.runTest {
+ val mockInfoAddress = arrayOf(mockTestAddressInfo(TEST_ADDRESS))
+ cachedDevice.stub {
+ on { address } doReturn TEST_ADDRESS
+ on { profiles } doReturn listOf(hapClientProfile)
+ }
+ audioManager.stub {
+ on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn mockInfoAddress
+ }
+
+ var result: Boolean? = null
+ underTest.isInputRoutingControlAvailable(
+ object : InputRoutingControlAvailableCallback {
+ override fun onResult(available: Boolean) {
+ result = available
+ }
+ }
+ )
+
+ runCurrent()
+ assertThat(result).isTrue()
+ }
+ }
+
+ @Test
+ fun isInputRoutingControlAvailable_notSupportHapProfile_returnFalse() {
+ testScope.runTest {
+ val mockInfoAddress = arrayOf(mockTestAddressInfo(TEST_ADDRESS))
+ cachedDevice.stub {
+ on { address } doReturn TEST_ADDRESS
+ on { profiles } doReturn emptyList()
+ }
+ audioManager.stub {
+ on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn mockInfoAddress
+ }
+
+ var result: Boolean? = null
+ underTest.isInputRoutingControlAvailable(
+ object : InputRoutingControlAvailableCallback {
+ override fun onResult(available: Boolean) {
+ result = available
+ }
+ }
+ )
+
+ runCurrent()
+ assertThat(result).isFalse()
+ }
+ }
+
+ @Test
+ fun isInputRoutingControlAvailable_validInputMember_supportHapProfile_returnTrue() {
+ testScope.runTest {
+ val mockInfoAddress2 = arrayOf(mockTestAddressInfo(TEST_ADDRESS_2))
+ cachedDevice.stub {
+ on { address } doReturn TEST_ADDRESS
+ on { profiles } doReturn listOf(hapClientProfile)
+ on { memberDevice } doReturn (setOf(memberCachedDevice))
+ }
+ memberCachedDevice.stub { on { address } doReturn TEST_ADDRESS_2 }
+ audioManager.stub {
+ on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn mockInfoAddress2
+ }
+
+ var result: Boolean? = null
+ underTest.isInputRoutingControlAvailable(
+ object : InputRoutingControlAvailableCallback {
+ override fun onResult(available: Boolean) {
+ result = available
+ }
+ }
+ )
+
+ runCurrent()
+ assertThat(result).isTrue()
+ }
+ }
+
+ @Test
+ fun isAvailable_notValidInputDevice_returnFalse() {
+ testScope.runTest {
+ cachedDevice.stub {
+ on { address } doReturn TEST_ADDRESS
+ on { profiles } doReturn listOf(hapClientProfile)
+ }
+ audioManager.stub {
+ on { getDevices(AudioManager.GET_DEVICES_INPUTS) } doReturn emptyArray()
+ }
+
+ var result: Boolean? = null
+ underTest.isInputRoutingControlAvailable(
+ object : InputRoutingControlAvailableCallback {
+ override fun onResult(available: Boolean) {
+ result = available
+ }
+ }
+ )
+
+ runCurrent()
+ assertThat(result).isFalse()
+ }
+ }
+
+ @Test
+ fun selectInputRouting_builtinMic_setMicrophonePreferredForCallsFalse() {
+ underTest.selectInputRouting(
+ HearingDevicesInputRoutingController.InputRoutingValue.BUILTIN_MIC.ordinal
+ )
+
+ verify(btDevice).isMicrophonePreferredForCalls = false
+ }
+
+ private fun mockTestAddressInfo(address: String): AudioDeviceInfo {
+ val info: AudioDeviceInfo = mock()
+ info.stub {
+ on { type } doReturn AudioDeviceInfo.TYPE_BLE_HEADSET
+ on { this.address } doReturn address
+ }
+
+ return info
+ }
+
+ companion object {
+ private const val TEST_ADDRESS = "55:66:77:88:99:AA"
+ private const val TEST_ADDRESS_2 = "55:66:77:88:99:BB"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
index 8d3640d8d809..53b364c13063 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
@@ -41,21 +41,9 @@ class FontVariationUtilsTest : SysuiTestCase() {
@Test
fun testStyleValueUnchange_getBlankStr() {
val fontVariationUtils = FontVariationUtils()
- fontVariationUtils.updateFontVariation(
- weight = 100,
- width = 100,
- opticalSize = 0,
- roundness = 100,
- )
- val updatedFvar1 =
- fontVariationUtils.updateFontVariation(
- weight = 100,
- width = 100,
- opticalSize = 0,
- roundness = 100,
- )
- Assert.assertEquals("", updatedFvar1)
- val updatedFvar2 = fontVariationUtils.updateFontVariation()
- Assert.assertEquals("", updatedFvar2)
+ Assert.assertEquals("", fontVariationUtils.updateFontVariation())
+ val fVar = fontVariationUtils.updateFontVariation(weight = 100)
+ Assert.assertEquals(fVar, fontVariationUtils.updateFontVariation())
+ Assert.assertEquals(fVar, fontVariationUtils.updateFontVariation(weight = 100))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 0cfb36d58f89..446891a7873e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -531,20 +531,23 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testLayoutParams_hasSecureWindowFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
}
@Test
fun testLayoutParams_hasShowWhenLockedFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
.isTrue()
}
@Test
fun testLayoutParams_hasDimbehindWindowFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
val lpFlags = layoutParams.flags
val lpDimAmount = layoutParams.dimAmount
@@ -554,7 +557,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testLayoutParams_excludesImeInsets() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
}
@@ -706,7 +710,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testLayoutParams_hasCutoutModeAlwaysFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
val lpFlags = layoutParams.flags
assertThat(
@@ -717,7 +722,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testLayoutParams_excludesSystemBarInsets() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.systemBars()) == 0).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index aeea99be40dd..a2f5a30a20ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -193,8 +193,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@Mock
- private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
- @Mock
private SelectedUserInteractor mSelectedUserInteractor;
// Capture listeners so that they can be used to send events
@@ -321,7 +319,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
mAlternateBouncerInteractor,
mInputManager,
mock(DeviceEntryFaceAuthInteractor.class),
- mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
mKeyguardTransitionInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
deleted file mode 100644
index 921ff098753e..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.testing.TestableLooper
-import android.view.View
-import android.view.accessibility.AccessibilityNodeInfo
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.util.mockito.argumentCaptor
-import org.junit.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@TestableLooper.RunWithLooper
-class UdfpsKeyguardAccessibilityDelegateTest : SysuiTestCase() {
-
- @Mock private lateinit var keyguardViewManager: StatusBarKeyguardViewManager
- @Mock private lateinit var hostView: View
- private lateinit var underTest: UdfpsKeyguardAccessibilityDelegate
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest =
- UdfpsKeyguardAccessibilityDelegate(
- context.resources,
- keyguardViewManager,
- )
- }
-
- @Test
- fun onInitializeAccessibilityNodeInfo_clickActionAdded() {
- // WHEN node is initialized
- val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
- underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
-
- // THEN a11y action is added
- val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
- verify(mockedNodeInfo).addAction(argumentCaptor.capture())
-
- // AND the a11y action is a click action
- assertEquals(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
- argumentCaptor.value.id
- )
- }
-
- @Test
- fun performAccessibilityAction_actionClick_showsPrimaryBouncer() {
- // WHEN click action is performed
- val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
- underTest.performAccessibilityAction(
- hostView,
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
- null
- )
-
- // THEN primary bouncer shows
- verify(keyguardViewManager).showPrimaryBouncer(anyBoolean())
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 5249bbe2a861..e8c30bafbba0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -58,7 +58,6 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.firstValue
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -116,12 +115,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
runCurrent()
verify(kosmos.windowManager).addView(any(), any())
-
verify(kosmos.windowManager).addView(viewCaptor.capture(), any())
- verify(viewCaptor.firstValue)
- .announceForAccessibility(
- mContext.getText(R.string.accessibility_side_fingerprint_indicator_label)
- )
updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = false)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
index 25a287c4cfff..15a6de896e92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
@@ -247,20 +247,20 @@ class PinInputViewModelTest : SysuiTestCase() {
}
private class PinInputSubject
-private constructor(metadata: FailureMetadata, private val actual: PinInputViewModel) :
+private constructor(metadata: FailureMetadata, private val actual: PinInputViewModel?) :
Subject(metadata, actual) {
fun matches(mnemonics: String) {
val actualMnemonics =
- actual.input
- .map { entry ->
+ actual?.input
+ ?.map { entry ->
when (entry) {
is Digit -> entry.input.digitToChar()
is ClearAll -> 'C'
else -> throw IllegalArgumentException()
}
}
- .joinToString(separator = "")
+ ?.joinToString(separator = "")
if (mnemonics != actualMnemonics) {
failWithActual(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt
new file mode 100644
index 000000000000..652a2ff21e9b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.ClipData
+import android.content.ComponentName
+import android.content.Intent
+import android.net.Uri
+import android.text.SpannableString
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActionIntentCreatorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+
+ val creator = ActionIntentCreator(testScope.backgroundScope)
+
+ @Test
+ fun test_getTextEditorIntent() {
+ val intent = creator.getTextEditorIntent(context)
+ assertEquals(ComponentName(context, EditTextActivity::class.java), intent.component)
+ assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ }
+
+ @Test
+ fun test_getRemoteCopyIntent() {
+ context.getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, "")
+
+ val clipData = ClipData.newPlainText("Test", "Test Item")
+ var intent = creator.getRemoteCopyIntent(clipData, context)
+
+ assertEquals(null, intent.component)
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+ assertEquals(clipData, intent.clipData)
+
+ // Try again with a remote copy component
+ val fakeComponent =
+ ComponentName("com.android.remotecopy", "com.android.remotecopy.RemoteCopyActivity")
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_remoteCopyPackage, fakeComponent.flattenToString())
+
+ intent = creator.getRemoteCopyIntent(clipData, context)
+ assertEquals(fakeComponent, intent.component)
+ }
+
+ @Test
+ fun test_getImageEditIntent() = runTest {
+ context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "")
+ val fakeUri = Uri.parse("content://foo")
+ var intent = creator.getImageEditIntent(fakeUri, context)
+
+ assertEquals(Intent.ACTION_EDIT, intent.action)
+ assertEquals("image/*", intent.type)
+ assertEquals(null, intent.component)
+ assertEquals("clipboard", intent.getStringExtra("edit_source"))
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+
+ // try again with an editor component
+ val fakeComponent =
+ ComponentName("com.android.remotecopy", "com.android.remotecopy.RemoteCopyActivity")
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString())
+ intent = creator.getImageEditIntent(fakeUri, context)
+ assertEquals(fakeComponent, intent.component)
+ }
+
+ @Test
+ fun test_getShareIntent_plaintext() {
+ val clipData = ClipData.newPlainText("Test", "Test Item")
+ val intent = creator.getShareIntent(clipData, context)
+
+ assertEquals(Intent.ACTION_CHOOSER, intent.action)
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+ val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertEquals("Test Item", target?.getStringExtra(Intent.EXTRA_TEXT))
+ assertEquals("text/plain", target?.type)
+ }
+
+ @Test
+ fun test_getShareIntent_html() {
+ val clipData = ClipData.newHtmlText("Test", "Some HTML", "<b>Some HTML</b>")
+ val intent = creator.getShareIntent(clipData, getContext())
+
+ assertEquals(Intent.ACTION_CHOOSER, intent.action)
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+ val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertEquals("Some HTML", target?.getStringExtra(Intent.EXTRA_TEXT))
+ assertEquals("text/plain", target?.type)
+ }
+
+ @Test
+ fun test_getShareIntent_image() {
+ val uri = Uri.parse("content://something")
+ val clipData = ClipData("Test", arrayOf("image/png"), ClipData.Item(uri))
+ val intent = creator.getShareIntent(clipData, context)
+
+ assertEquals(Intent.ACTION_CHOOSER, intent.action)
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+ val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertEquals(uri, target?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+ assertEquals(uri, target?.clipData?.getItemAt(0)?.uri)
+ assertEquals("image/png", target?.type)
+ }
+
+ @Test
+ fun test_getShareIntent_spannableText() {
+ val clipData = ClipData.newPlainText("Test", SpannableString("Test Item"))
+ val intent = creator.getShareIntent(clipData, context)
+
+ assertEquals(Intent.ACTION_CHOOSER, intent.action)
+ assertFlags(intent, EXTERNAL_INTENT_FLAGS)
+ val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertEquals("Test Item", target?.getStringExtra(Intent.EXTRA_TEXT))
+ assertEquals("text/plain", target?.type)
+ }
+
+ // Assert that the given flags are set
+ private fun assertFlags(intent: Intent, flags: Int) {
+ assertTrue((intent.flags and flags) == flags)
+ }
+
+ companion object {
+ private const val EXTERNAL_INTENT_FLAGS: Int =
+ (Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java
index ea6cb3b6d178..10c5c52d21a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java
@@ -34,15 +34,19 @@ import com.android.systemui.res.R;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.atomic.AtomicReference;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class IntentCreatorTest extends SysuiTestCase {
+public class DefaultIntentCreatorTest extends SysuiTestCase {
private static final int EXTERNAL_INTENT_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ private final DefaultIntentCreator mIntentCreator = new DefaultIntentCreator();
+
@Test
public void test_getTextEditorIntent() {
- Intent intent = IntentCreator.getTextEditorIntent(getContext());
+ Intent intent = mIntentCreator.getTextEditorIntent(getContext());
assertEquals(new ComponentName(getContext(), EditTextActivity.class),
intent.getComponent());
assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -54,7 +58,7 @@ public class IntentCreatorTest extends SysuiTestCase {
"");
ClipData clipData = ClipData.newPlainText("Test", "Test Item");
- Intent intent = IntentCreator.getRemoteCopyIntent(clipData, getContext());
+ Intent intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext());
assertEquals(null, intent.getComponent());
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
@@ -66,17 +70,21 @@ public class IntentCreatorTest extends SysuiTestCase {
getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage,
fakeComponent.flattenToString());
- intent = IntentCreator.getRemoteCopyIntent(clipData, getContext());
+ intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext());
assertEquals(fakeComponent, intent.getComponent());
}
@Test
- public void test_getImageEditIntent() {
+ public void test_getImageEditIntentAsync() {
getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
"");
Uri fakeUri = Uri.parse("content://foo");
- Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext());
+ final AtomicReference<Intent> intentHolder = new AtomicReference<>(null);
+ mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> {
+ intentHolder.set(output);
+ });
+ Intent intent = intentHolder.get();
assertEquals(Intent.ACTION_EDIT, intent.getAction());
assertEquals("image/*", intent.getType());
assertEquals(null, intent.getComponent());
@@ -88,14 +96,16 @@ public class IntentCreatorTest extends SysuiTestCase {
"com.android.remotecopy.RemoteCopyActivity");
getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
fakeComponent.flattenToString());
- intent = IntentCreator.getImageEditIntent(fakeUri, getContext());
- assertEquals(fakeComponent, intent.getComponent());
+ mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> {
+ intentHolder.set(output);
+ });
+ assertEquals(fakeComponent, intentHolder.get().getComponent());
}
@Test
public void test_getShareIntent_plaintext() {
ClipData clipData = ClipData.newPlainText("Test", "Test Item");
- Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+ Intent intent = mIntentCreator.getShareIntent(clipData, getContext());
assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
@@ -108,7 +118,7 @@ public class IntentCreatorTest extends SysuiTestCase {
public void test_getShareIntent_html() {
ClipData clipData = ClipData.newHtmlText("Test", "Some HTML",
"<b>Some HTML</b>");
- Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+ Intent intent = mIntentCreator.getShareIntent(clipData, getContext());
assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
@@ -122,7 +132,7 @@ public class IntentCreatorTest extends SysuiTestCase {
Uri uri = Uri.parse("content://something");
ClipData clipData = new ClipData("Test", new String[]{"image/png"},
new ClipData.Item(uri));
- Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+ Intent intent = mIntentCreator.getShareIntent(clipData, getContext());
assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
@@ -135,7 +145,7 @@ public class IntentCreatorTest extends SysuiTestCase {
@Test
public void test_getShareIntent_spannableText() {
ClipData clipData = ClipData.newPlainText("Test", new SpannableString("Test Item"));
- Intent intent = IntentCreator.getShareIntent(clipData, getContext());
+ Intent intent = mIntentCreator.getShareIntent(clipData, getContext());
assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt
new file mode 100644
index 000000000000..f64f13d4a9b5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.model.StateChange
+import com.android.systemui.model.fakeSysUIStatePerDisplayRepository
+import com.android.systemui.model.sysUiStateFactory
+import com.android.systemui.model.sysuiStateInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStatePerDisplayInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ val stateRepository = kosmos.fakeSysUIStatePerDisplayRepository
+ val state0 = kosmos.sysUiStateFactory.create(0)
+ val state1 = kosmos.sysUiStateFactory.create(1)
+ val state2 = kosmos.sysUiStateFactory.create(2)
+
+ val underTest = kosmos.sysuiStateInteractor
+
+ @Before
+ fun setup() {
+ stateRepository.apply {
+ add(0, state0)
+ add(1, state1)
+ add(2, state2)
+ }
+ runBlocking {
+ kosmos.displayRepository.apply {
+ addDisplay(0)
+ addDisplay(1)
+ addDisplay(2)
+ }
+ }
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_setsFlagsOnTargetStateAndClearsTheOthers() {
+ val targetDisplayId = 0
+ val stateChange = StateChange().setFlag(1L, true)
+
+ underTest.setFlagsExclusivelyToDisplay(targetDisplayId, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isTrue()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isTrue()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+
+ underTest.setFlagsExclusivelyToDisplay(2, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isTrue()
+
+ underTest.setFlagsExclusivelyToDisplay(3, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_multipleFlags_setsFlagsOnTargetStateAndClearsTheOthers() {
+ val stateChange = StateChange().setFlag(1L, true).setFlag(2L, true)
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state0.isFlagEnabled(2)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isTrue()
+ assertThat(state1.isFlagEnabled(2)).isTrue()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_clearsFlags() {
+ state0.setFlag(1, true).setFlag(2, true).commitUpdate()
+ state1.setFlag(1, true).setFlag(2, true).commitUpdate()
+ state2.setFlag(1, true).setFlag(2, true).commitUpdate()
+
+ val stateChange = StateChange().setFlag(1L, false)
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ // Sets it as false in display 1, but also the others.
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt
index 0f400892f988..56b06de0a9ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt
@@ -17,14 +17,12 @@
package com.android.systemui.common.ui.view
+import android.testing.TestableLooper
+import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel
-import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel.Down
-import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel.Move
-import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel.Up
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -33,18 +31,22 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class TouchHandlingViewInteractionHandlerTest : SysuiTestCase() {
@Mock private lateinit var postDelayed: (Runnable, Long) -> DisposableHandle
@Mock private lateinit var onLongPressDetected: (Int, Int) -> Unit
@Mock private lateinit var onSingleTapDetected: (Int, Int) -> Unit
+ @Mock private lateinit var onDoubleTapDetected: () -> Unit
private lateinit var underTest: TouchHandlingViewInteractionHandler
@@ -61,14 +63,17 @@ class TouchHandlingViewInteractionHandlerTest : SysuiTestCase() {
underTest =
TouchHandlingViewInteractionHandler(
+ context = context,
postDelayed = postDelayed,
isAttachedToWindow = { isAttachedToWindow },
onLongPressDetected = onLongPressDetected,
onSingleTapDetected = onSingleTapDetected,
+ onDoubleTapDetected = onDoubleTapDetected,
longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() },
allowedTouchSlop = ViewConfiguration.getTouchSlop(),
)
underTest.isLongPressHandlingEnabled = true
+ underTest.isDoubleTapHandlingEnabled = true
}
@Test
@@ -76,63 +81,250 @@ class TouchHandlingViewInteractionHandlerTest : SysuiTestCase() {
val downX = 123
val downY = 456
dispatchTouchEvents(
- Down(x = downX, y = downY),
- Move(distanceMoved = ViewConfiguration.getTouchSlop() - 0.1f),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_MOVE,
+ 123f + ViewConfiguration.getTouchSlop() - 0.1f,
+ 456f,
+ 0,
+ ),
)
delayedRunnable?.run()
verify(onLongPressDetected).invoke(downX, downY)
- verify(onSingleTapDetected, never()).invoke(any(), any())
+ verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt())
}
@Test
fun longPressButFeatureNotEnabled() = runTest {
underTest.isLongPressHandlingEnabled = false
- dispatchTouchEvents(Down(x = 123, y = 456))
+ dispatchTouchEvents(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0))
assertThat(delayedRunnable).isNull()
- verify(onLongPressDetected, never()).invoke(any(), any())
- verify(onSingleTapDetected, never()).invoke(any(), any())
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt())
}
@Test
fun longPressButViewNotAttached() = runTest {
isAttachedToWindow = false
- dispatchTouchEvents(Down(x = 123, y = 456))
+ dispatchTouchEvents(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0))
delayedRunnable?.run()
- verify(onLongPressDetected, never()).invoke(any(), any())
- verify(onSingleTapDetected, never()).invoke(any(), any())
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt())
}
@Test
fun draggedTooFarToBeConsideredAlongPress() = runTest {
dispatchTouchEvents(
- Down(x = 123, y = 456),
- Move(distanceMoved = ViewConfiguration.getTouchSlop() + 0.1f),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123F, 456F, 0),
+ // Drag action within touch slop
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 123f, 456f, 0).apply {
+ addBatch(0L, 123f + ViewConfiguration.getTouchSlop() + 0.1f, 456f, 0f, 0f, 0)
+ },
)
assertThat(delayedRunnable).isNull()
- verify(onLongPressDetected, never()).invoke(any(), any())
- verify(onSingleTapDetected, never()).invoke(any(), any())
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt())
}
@Test
fun heldDownTooBrieflyToBeConsideredAlongPress() = runTest {
dispatchTouchEvents(
- Down(x = 123, y = 456),
- Up(
- distanceMoved = ViewConfiguration.getTouchSlop().toFloat(),
- gestureDuration = ViewConfiguration.getLongPressTimeout() - 1L,
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(
+ 0L,
+ ViewConfiguration.getLongPressTimeout() - 1L,
+ MotionEvent.ACTION_UP,
+ 123f,
+ 456F,
+ 0,
),
)
assertThat(delayedRunnable).isNull()
- verify(onLongPressDetected, never()).invoke(any(), any())
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
verify(onSingleTapDetected).invoke(123, 456)
}
- private fun dispatchTouchEvents(vararg models: MotionEventModel) {
- models.forEach { model -> underTest.onTouchEvent(model) }
+ @Test
+ fun doubleTap() = runTest {
+ val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L
+ dispatchTouchEvents(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_DOWN,
+ 123f,
+ 456f,
+ 0,
+ ),
+ MotionEvent.obtain(secondTapTime, secondTapTime, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ )
+
+ verify(onDoubleTapDetected).invoke()
+ assertThat(delayedRunnable).isNull()
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt())
+ }
+
+ @Test
+ fun doubleTapButFeatureNotEnabled() = runTest {
+ underTest.isDoubleTapHandlingEnabled = false
+
+ val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L
+ dispatchTouchEvents(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_DOWN,
+ 123f,
+ 456f,
+ 0,
+ ),
+ MotionEvent.obtain(secondTapTime, secondTapTime, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ )
+
+ verify(onDoubleTapDetected, never()).invoke()
+ assertThat(delayedRunnable).isNull()
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt())
+ }
+
+ @Test
+ fun tapIntoLongPress() = runTest {
+ val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L
+ dispatchTouchEvents(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_DOWN,
+ 123f,
+ 456f,
+ 0,
+ ),
+ MotionEvent.obtain(
+ secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L,
+ secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L,
+ MotionEvent.ACTION_MOVE,
+ 123f + ViewConfiguration.getTouchSlop() - 0.1f,
+ 456f,
+ 0,
+ ),
+ )
+ delayedRunnable?.run()
+
+ verify(onDoubleTapDetected, never()).invoke()
+ verify(onSingleTapDetected).invoke(anyInt(), anyInt())
+ verify(onLongPressDetected).invoke(anyInt(), anyInt())
+ }
+
+ @Test
+ fun tapIntoDownHoldTooBrieflyToBeConsideredLongPress() = runTest {
+ val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L
+ dispatchTouchEvents(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_DOWN,
+ 123f,
+ 456f,
+ 0,
+ ),
+ MotionEvent.obtain(
+ secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L,
+ secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L,
+ MotionEvent.ACTION_UP,
+ 123f,
+ 456f,
+ 0,
+ ),
+ )
+ delayedRunnable?.run()
+
+ verify(onDoubleTapDetected, never()).invoke()
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt())
+ }
+
+ @Test
+ fun tapIntoDrag() = runTest {
+ val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L
+ dispatchTouchEvents(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_DOWN,
+ 123f,
+ 456f,
+ 0,
+ ),
+ // Drag event within touch slop
+ MotionEvent.obtain(secondTapTime, secondTapTime, MotionEvent.ACTION_MOVE, 123f, 456f, 0)
+ .apply {
+ addBatch(
+ secondTapTime,
+ 123f + ViewConfiguration.getTouchSlop() + 0.1f,
+ 456f,
+ 0f,
+ 0f,
+ 0,
+ )
+ },
+ )
+ delayedRunnable?.run()
+
+ verify(onDoubleTapDetected, never()).invoke()
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected).invoke(anyInt(), anyInt())
+ }
+
+ @Test
+ fun doubleTapOutOfAllowableSlop() = runTest {
+ val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L
+ val scaledDoubleTapSlop = ViewConfiguration.get(context).scaledDoubleTapSlop
+ dispatchTouchEvents(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0),
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_DOWN,
+ 123f + scaledDoubleTapSlop + 0.1f,
+ 456f + scaledDoubleTapSlop + 0.1f,
+ 0,
+ ),
+ MotionEvent.obtain(
+ secondTapTime,
+ secondTapTime,
+ MotionEvent.ACTION_UP,
+ 123f + scaledDoubleTapSlop + 0.1f,
+ 456f + scaledDoubleTapSlop + 0.1f,
+ 0,
+ ),
+ )
+
+ verify(onDoubleTapDetected, never()).invoke()
+ assertThat(delayedRunnable).isNull()
+ verify(onLongPressDetected, never()).invoke(anyInt(), anyInt())
+ verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt())
+ }
+
+ private fun dispatchTouchEvents(vararg events: MotionEvent) {
+ events.forEach { event -> underTest.onTouchEvent(event) }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
index ed73d89db2c7..6a25069a4e5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
@@ -73,12 +73,12 @@ class CommunalOngoingContentStartableTest : SysuiTestCase() {
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
- kosmos.setCommunalEnabled(true)
+ setCommunalEnabled(true)
assertThat(fakeCommunalMediaRepository.isListening()).isTrue()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
- kosmos.setCommunalEnabled(false)
+ setCommunalEnabled(false)
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
@@ -93,13 +93,13 @@ class CommunalOngoingContentStartableTest : SysuiTestCase() {
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
- kosmos.setCommunalEnabled(true)
+ setCommunalEnabled(true)
// Media listening does not start when UMO is disabled.
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
- kosmos.setCommunalEnabled(false)
+ setCommunalEnabled(false)
assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt
new file mode 100644
index 000000000000..f9b29e9bc5b5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.repository
+
+import android.app.UiModeManager
+import android.app.UiModeManager.OnProjectionStateChangedListener
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarProjectionRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val capturedListeners = mutableListOf<OnProjectionStateChangedListener>()
+
+ private val Kosmos.uiModeManager by
+ Kosmos.Fixture<UiModeManager> {
+ mock {
+ on {
+ addOnProjectionStateChangedListener(
+ eq(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+ any(),
+ any(),
+ )
+ } doAnswer
+ {
+ val listener = it.getArgument<OnProjectionStateChangedListener>(2)
+ capturedListeners.add(listener)
+ Unit
+ }
+
+ on { removeOnProjectionStateChangedListener(any()) } doAnswer
+ {
+ val listener = it.getArgument<OnProjectionStateChangedListener>(0)
+ capturedListeners.remove(listener)
+ Unit
+ }
+
+ on { activeProjectionTypes } doReturn UiModeManager.PROJECTION_TYPE_NONE
+ }
+ }
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CarProjectionRepositoryImpl(
+ uiModeManager = uiModeManager,
+ bgDispatcher = testDispatcher,
+ )
+ }
+
+ @Test
+ fun testProjectionActiveUpdatesAfterCallback() =
+ kosmos.runTest {
+ val projectionActive by collectLastValue(underTest.projectionActive)
+ assertThat(projectionActive).isFalse()
+
+ setActiveProjectionType(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+ assertThat(projectionActive).isTrue()
+
+ setActiveProjectionType(UiModeManager.PROJECTION_TYPE_NONE)
+ assertThat(projectionActive).isFalse()
+ }
+
+ @Test
+ fun testProjectionInitialValueTrue() =
+ kosmos.runTest {
+ setActiveProjectionType(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+
+ val projectionActive by collectLastValue(underTest.projectionActive)
+ assertThat(projectionActive).isTrue()
+ }
+
+ @Test
+ fun testUnsubscribeWhenCancelled() =
+ kosmos.runTest {
+ val job = underTest.projectionActive.launchIn(backgroundScope)
+ assertThat(capturedListeners).hasSize(1)
+
+ job.cancel()
+ assertThat(capturedListeners).isEmpty()
+ }
+
+ private fun Kosmos.setActiveProjectionType(@UiModeManager.ProjectionType projectionType: Int) {
+ uiModeManager.stub { on { activeProjectionTypes } doReturn projectionType }
+ capturedListeners.forEach { it.onProjectionStateChanged(projectionType, emptySet()) }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 5c983656225e..09d44a5e18d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -34,9 +34,7 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.communal.data.model.DisabledReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
-import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
@@ -202,63 +200,6 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
- fun secondaryUserIsInvalid() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
-
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
- @Test
- fun classicFlagIsDisabled() =
- kosmos.runTest {
- setCommunalV2Enabled(false)
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
- }
-
- @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
- @Test
- fun communalHubFlagIsDisabled() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsDisabledByUser() =
- kosmos.runTest {
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
-
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
- assertThat(enabledState?.enabled).isFalse()
-
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
- assertThat(enabledState?.enabled).isTrue()
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsDisabledByDevicePolicy() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isTrue()
-
- setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
kosmos.runTest {
val widgetsAllowedForWorkProfile by
@@ -269,36 +210,6 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
assertThat(widgetsAllowedForWorkProfile).isFalse()
}
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
- kosmos.runTest {
- val enabledStateForPrimaryUser by
- collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
-
- setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
- assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubIsDisabledByUserAndDevicePolicy() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
- assertThat(enabledState?.enabled).isTrue()
-
- fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
- setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
-
- assertThat(enabledState?.enabled).isFalse()
- assertThat(enabledState)
- .containsExactly(
- DisabledReason.DISABLED_REASON_DEVICE_POLICY,
- DisabledReason.DISABLED_REASON_USER_SETTING,
- )
- }
-
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun backgroundType_defaultValue() =
@@ -327,26 +238,6 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
- fun screensaverDisabledByUser() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
-
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id)
-
- assertThat(enabledState).isFalse()
- }
-
- @Test
- fun screensaverEnabledByUser() =
- kosmos.runTest {
- val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
-
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id)
-
- assertThat(enabledState).isTrue()
- }
-
- @Test
fun whenToDream_charging() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt
new file mode 100644
index 000000000000..fc4cd43577b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.carProjectionRepository
+import com.android.systemui.communal.data.repository.fake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarProjectionInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture { carProjectionInteractor }
+
+ @Test
+ fun testProjectionActive() =
+ kosmos.runTest {
+ val projectionActive by collectLastValue(underTest.projectionActive)
+ assertThat(projectionActive).isFalse()
+
+ carProjectionRepository.fake.setProjectionActive(true)
+ assertThat(projectionActive).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt
new file mode 100644
index 000000000000..f4a1c90a5471
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.posturing.data.repository.fake
+import com.android.systemui.communal.posturing.data.repository.posturingRepository
+import com.android.systemui.communal.posturing.shared.model.PosturedState
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAutoOpenInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture { communalAutoOpenInteractor }
+
+ @Before
+ fun setUp() {
+ runBlocking { kosmos.fakeUserRepository.asMainUser() }
+ with(kosmos.fakeSettings) {
+ putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, false, MAIN_USER_ID)
+ putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, false, MAIN_USER_ID)
+ putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, false, MAIN_USER_ID)
+ }
+ }
+
+ @Test
+ fun testStartWhileCharging() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ true,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(false)
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ assertThat(shouldAutoOpen).isTrue()
+ assertThat(suppressionReason).isNull()
+ }
+
+ @Test
+ fun testStartWhileDocked() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ true,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ fakeDockManager.setIsDocked(false)
+
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+
+ fakeDockManager.setIsDocked(true)
+ fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+ assertThat(shouldAutoOpen).isTrue()
+ assertThat(suppressionReason).isNull()
+ }
+
+ @Test
+ fun testStartWhilePostured() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ true,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
+
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+
+ posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+ assertThat(shouldAutoOpen).isTrue()
+ assertThat(suppressionReason).isNull()
+ }
+
+ @Test
+ fun testStartNever() =
+ kosmos.runTest {
+ val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+ val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ false,
+ MAIN_USER_ID,
+ )
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ false,
+ MAIN_USER_ID,
+ )
+ fakeSettings.putBoolForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ false,
+ MAIN_USER_ID,
+ )
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+ fakeDockManager.setIsDocked(true)
+
+ assertThat(shouldAutoOpen).isFalse()
+ assertThat(suppressionReason)
+ .isEqualTo(
+ SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
deleted file mode 100644
index beec184b80e7..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private lateinit var communalRepository: FakeCommunalSceneRepository
- private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
-
- private lateinit var underTest: CommunalInteractor
-
- @Before
- fun setUp() {
- communalRepository = kosmos.fakeCommunalSceneRepository
- widgetRepository = kosmos.fakeCommunalWidgetRepository
- keyguardRepository = kosmos.fakeKeyguardRepository
-
- mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
-
- underTest = kosmos.communalInteractor
- }
-
- @Test
- fun isCommunalEnabled_false() =
- testScope.runTest { assertThat(underTest.isCommunalEnabled.value).isFalse() }
-
- @Test
- fun isCommunalAvailable_whenStorageUnlock_false() =
- testScope.runTest {
- val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
-
- assertThat(isCommunalAvailable).isFalse()
-
- keyguardRepository.setIsEncryptedOrLockdown(false)
- runCurrent()
-
- assertThat(isCommunalAvailable).isFalse()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 8424746f3db5..b65ecf46dcca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -21,7 +21,6 @@ import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
-import android.content.res.mainResources
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
@@ -39,9 +38,8 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
@@ -50,14 +48,9 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
-import com.android.systemui.communal.posturing.data.repository.fake
-import com.android.systemui.communal.posturing.data.repository.posturingRepository
-import com.android.systemui.communal.posturing.shared.model.PosturedState
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -75,19 +68,16 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.statusbar.phone.fakeManagedProfileController
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,10 +88,6 @@ import org.mockito.Mockito.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-/**
- * This class of test cases assume that communal is enabled. For disabled cases, see
- * [CommunalInteractorCommunalDisabledTest].
- */
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -109,10 +95,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
- private val kosmos =
- testKosmos()
- .apply { mainResources = mContext.orCreateTestableResources.resources }
- .useUnconfinedTestDispatcher()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
@@ -128,104 +111,40 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
- false,
- )
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
- false,
- )
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
- false,
- )
- }
-
- @After
- fun tearDown() {
- mContext.orCreateTestableResources.removeOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
- )
- mContext.orCreateTestableResources.removeOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
- )
- mContext.orCreateTestableResources.removeOverride(
- com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
- )
}
@Test
fun communalEnabled_true() =
kosmos.runTest {
- fakeUserRepository.setSelectedUserInfo(mainUser)
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
assertThat(underTest.isCommunalEnabled.value).isTrue()
}
@Test
- fun isCommunalAvailable_mainUserUnlockedAndMainUser_true() =
+ fun isCommunalAvailable_whenKeyguardShowing_true() =
kosmos.runTest {
- val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
-
- assertThat(isAvailable).isTrue()
- }
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
+ fakeKeyguardRepository.setKeyguardShowing(false)
- @Test
- fun isCommunalAvailable_mainUserLockedAndMainUser_false() =
- kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false)
- fakeUserRepository.setSelectedUserInfo(mainUser)
fakeKeyguardRepository.setKeyguardShowing(true)
-
- assertThat(isAvailable).isFalse()
+ assertThat(isAvailable).isTrue()
}
@Test
- fun isCommunalAvailable_mainUserUnlockedAndSecondaryUser_false() =
+ fun isCommunalAvailable_suppressed() =
kosmos.runTest {
- val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(secondaryUser)
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
fakeKeyguardRepository.setKeyguardShowing(true)
- assertThat(isAvailable).isFalse()
- }
-
- @Test
- fun isCommunalAvailable_whenKeyguardShowing_true() =
- kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
-
assertThat(isAvailable).isTrue()
- }
-
- @Test
- fun isCommunalAvailable_communalDisabled_false() =
- kosmos.runTest {
- mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
- val isAvailable by collectLastValue(underTest.isCommunalAvailable)
- assertThat(isAvailable).isFalse()
-
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown())
+ )
assertThat(isAvailable).isFalse()
}
@@ -1280,66 +1199,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
.inOrder()
}
- @Test
- fun showCommunalWhileCharging() =
- kosmos.runTest {
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 1,
- mainUser.id,
- )
-
- val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
- batteryRepository.fake.setDevicePluggedIn(false)
- assertThat(shouldShowCommunal).isFalse()
-
- batteryRepository.fake.setDevicePluggedIn(true)
- assertThat(shouldShowCommunal).isTrue()
- }
-
- @Test
- fun showCommunalWhilePosturedAndCharging() =
- kosmos.runTest {
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
- 1,
- mainUser.id,
- )
-
- val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
- batteryRepository.fake.setDevicePluggedIn(true)
- posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
- assertThat(shouldShowCommunal).isFalse()
-
- posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
- assertThat(shouldShowCommunal).isTrue()
- }
-
- @Test
- fun showCommunalWhileDocked() =
- kosmos.runTest {
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(mainUser)
- fakeKeyguardRepository.setKeyguardShowing(true)
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id)
-
- batteryRepository.fake.setDevicePluggedIn(true)
- fakeDockManager.setIsDocked(false)
-
- val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
- assertThat(shouldShowCommunal).isFalse()
-
- fakeDockManager.setIsDocked(true)
- fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
- assertThat(shouldShowCommunal).isTrue()
- }
-
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index f47aa6b3dc03..b8dbc9f77076 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -33,6 +33,8 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
@@ -48,6 +50,8 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.model.CommunalContentSize
@@ -73,6 +77,8 @@ import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
@@ -122,7 +128,9 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+
@Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -206,11 +214,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
testScope.runTest {
- // Keyguard showing, storage unlocked, main user, and tutorial not started.
keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- userRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
- setIsMainUser(true)
+ kosmos.setCommunalEnabled(true)
tutorialRepository.setTutorialSettingState(
Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
)
@@ -940,6 +945,31 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(isUiBlurred).isFalse()
}
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun swipeToCommunal() =
+ kosmos.runTest {
+ setCommunalV2ConfigEnabled(true)
+ // Suppress manual opening
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_MANUAL_OPEN))
+ )
+
+ val viewModel = createViewModel()
+ val swipeToHubEnabled by collectLastValue(viewModel.swipeToHubEnabled)
+ assertThat(swipeToHubEnabled).isFalse()
+
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
+ assertThat(swipeToHubEnabled).isTrue()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ assertThat(swipeToHubEnabled).isFalse()
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index c15f797aad5d..df10d058c5d1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal.widgets
import android.content.pm.UserInfo
-import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
@@ -25,6 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
import com.android.systemui.communal.shared.model.FakeGlanceableHubMultiUserHelper
import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
import com.android.systemui.coroutines.collectLastValue
@@ -37,11 +37,9 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.domain.interactor.userLockedInteractor
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
@@ -282,22 +280,12 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
}
}
- private suspend fun setCommunalAvailable(
- available: Boolean,
- setKeyguardShowing: Boolean = true,
- ) =
+ private fun setCommunalAvailable(available: Boolean, setKeyguardShowing: Boolean = true) =
with(kosmos) {
- fakeUserRepository.setUserUnlocked(MAIN_USER_ID, true)
- fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ setCommunalEnabled(available)
if (setKeyguardShowing) {
fakeKeyguardRepository.setKeyguardShowing(true)
}
- val settingsValue = if (available) 1 else 0
- fakeSettings.putIntForUser(
- Settings.Secure.GLANCEABLE_HUB_ENABLED,
- settingsValue,
- MAIN_USER_INFO.id,
- )
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index e0515000b232..454c15667f22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.phone.dozeScrimController
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
@@ -105,7 +106,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
@Test
fun nonPowerButtonFPS_vibrateSuccess() =
testScope.runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
runCurrent()
enterDeviceFromFingerprintUnlockLegacy()
@@ -116,7 +117,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
@Test
fun powerButtonFPS_vibrateSuccess() =
testScope.runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
@@ -133,7 +134,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
@Test
fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
testScope.runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
@@ -150,7 +151,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
@Test
fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
testScope.runTest {
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
@@ -174,14 +175,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
}
@Test
- fun nonPowerButtonFPS_coExFaceFailure_vibrateError() =
+ fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
testScope.runTest {
val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
enrollFace()
runCurrent()
faceFailure()
- assertThat(playErrorHaptic).isNotNull()
+ assertThat(playErrorHaptic).isNull()
}
@Test
@@ -211,7 +212,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
testScope.runTest {
kosmos.configureKeyguardBypass(isBypassAvailable = false)
underTest = kosmos.deviceEntryHapticsInteractor
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
runCurrent()
configureDeviceEntryFromBiometricSource(isFpUnlock = true)
@@ -225,7 +226,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
testScope.runTest {
kosmos.configureKeyguardBypass(isBypassAvailable = false)
underTest = kosmos.deviceEntryHapticsInteractor
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
@@ -246,18 +247,19 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
enrollFace()
kosmos.configureKeyguardBypass(isBypassAvailable = true)
underTest = kosmos.deviceEntryHapticsInteractor
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
configureDeviceEntryFromBiometricSource(isFaceUnlock = true)
verifyDeviceEntryFromFaceAuth()
assertThat(playSuccessHaptic).isNotNull()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@EnableSceneContainer
@Test
- fun playSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() =
+ fun skipSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() =
testScope.runTest {
underTest = kosmos.deviceEntryHapticsInteractor
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFace()
kosmos.configureKeyguardBypass(isBypassAvailable = false)
@@ -265,7 +267,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
configureDeviceEntryFromBiometricSource(isFaceUnlock = true, bypassEnabled = false)
kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true
- assertThat(playSuccessHaptic).isNotNull()
+ assertThat(playSuccessHaptic).isNull()
}
@EnableSceneContainer
@@ -274,7 +276,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
testScope.runTest {
kosmos.configureKeyguardBypass(isBypassAvailable = false)
underTest = kosmos.deviceEntryHapticsInteractor
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
// power button is currently DOWN
kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
@@ -295,7 +297,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
testScope.runTest {
kosmos.configureKeyguardBypass(isBypassAvailable = false)
underTest = kosmos.deviceEntryHapticsInteractor
- val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 197b0eea05cb..859137507bbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -26,6 +26,7 @@ import android.view.Display.TYPE_INTERNAL
import android.view.IWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.displaylib.DisplayRepository.PendingDisplay
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
@@ -71,14 +72,20 @@ class DisplayRepositoryTest : SysuiTestCase() {
// that the initial state (soon after construction) contains the expected ones set in every
// test.
private val displayRepository: DisplayRepositoryImpl by lazy {
- DisplayRepositoryImpl(
+ // TODO b/401305290 - move this to kosmos
+ val displayRepositoryFromLib =
+ com.android.app.displaylib.DisplayRepositoryImpl(
displayManager,
- commandQueue,
- windowManager,
testHandler,
- TestScope(UnconfinedTestDispatcher()),
+ testScope.backgroundScope,
UnconfinedTestDispatcher(),
)
+ DisplayRepositoryImpl(
+ commandQueue,
+ windowManager,
+ testScope.backgroundScope,
+ displayRepositoryFromLib,
+ )
.also {
verify(displayManager, never()).registerDisplayListener(any(), any())
// It needs to be called, just once, for the initial value.
@@ -403,7 +410,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
val pendingDisplay by lastPendingDisplay()
sendOnDisplayConnected(1, TYPE_EXTERNAL)
- val initialPendingDisplay: DisplayRepository.PendingDisplay? = pendingDisplay
+ val initialPendingDisplay: PendingDisplay? = pendingDisplay
assertThat(pendingDisplay).isNotNull()
sendOnDisplayChanged(1)
@@ -416,7 +423,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
val pendingDisplay by lastPendingDisplay()
sendOnDisplayConnected(1, TYPE_EXTERNAL)
- val initialPendingDisplay: DisplayRepository.PendingDisplay? = pendingDisplay
+ val initialPendingDisplay: PendingDisplay? = pendingDisplay
assertThat(pendingDisplay).isNotNull()
sendOnDisplayConnected(2, TYPE_EXTERNAL)
@@ -648,7 +655,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
return flowValue
}
- private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
+ private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> {
val flowValue = collectLastValue(displayRepository.pendingDisplay)
captureAddedRemovedListener()
verify(displayManager)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
new file mode 100644
index 000000000000..6c7783ae44e1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.display.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val fakePerDisplayInstanceProviderWithTeardown =
+ kosmos.fakePerDisplayInstanceProviderWithTeardown
+
+ private val underTest: PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> =
+ kosmos.fakePerDisplayInstanceRepository
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository += createDisplay(DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository += createDisplay(NON_DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val instance = underTest[DEFAULT_DISPLAY_ID]
+
+ assertThat(underTest[DEFAULT_DISPLAY_ID]).isSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ fakeDisplayRepository -= NON_DEFAULT_DISPLAY_ID
+ fakeDisplayRepository += createDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNotSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonExistingDisplayId_returnsNull() =
+ testScope.runTest { assertThat(underTest[NON_EXISTING_DISPLAY_ID]).isNull() }
+
+ @Test
+ fun forDisplay_afterDisplayRemoved_destroyInstanceInvoked() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ fakeDisplayRepository -= NON_DEFAULT_DISPLAY_ID
+
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed)
+ .containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_destroyInstanceIsNotInvoked() =
+ testScope.runTest {
+ underTest[NON_DEFAULT_DISPLAY_ID]
+
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed).isEmpty()
+ }
+
+ @Test
+ fun start_registersDumpable() {
+ verify(kosmos.dumpManager).registerNormalDumpable(anyString(), any())
+ }
+
+ private fun createDisplay(displayId: Int): Display =
+ display(type = Display.TYPE_INTERNAL, id = displayId)
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
index f37306276848..efc68f3b6884 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -57,4 +57,17 @@ class KeyEventInteractorTest : SysuiTestCase() {
repository.setPowerButtonDown(true)
assertThat(isPowerDown).isTrue()
}
+
+ @Test
+ fun testPowerButtonBeingLongPressedInteractor() =
+ runTest {
+ val isPowerButtonLongPressed by collectLastValue(
+ underTest.isPowerButtonLongPressed)
+
+ repository.setPowerButtonLongPressed(false)
+ assertThat(isPowerButtonLongPressed).isFalse()
+
+ repository.setPowerButtonLongPressed(true)
+ assertThat(isPowerButtonLongPressed).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 909acca12551..573216dd680a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -49,9 +49,9 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SystemUIInitializerImpl;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -177,7 +177,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
@Test
public void schedulesAlarm12hBefore() {
long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(16);
- AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null);
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours,
+ null);
mProvider.onNextAlarmChanged(alarmClockInfo);
long twelveHours = TimeUnit.HOURS.toMillis(KeyguardSliceProvider.ALARM_VISIBILITY_HOURS);
@@ -189,7 +190,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
@Test
public void updatingNextAlarmInvalidatesSlice() {
long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(8);
- AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null);
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours,
+ null);
mProvider.onNextAlarmChanged(alarmClockInfo);
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -204,7 +206,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
@Test
public void addZenMode_addedToSlice() {
ListBuilder listBuilder = spy(new ListBuilder(getContext(), mProvider.getUri(),
- ListBuilder.INFINITY));
+ ListBuilder.INFINITY));
mProvider.addZenModeLocked(listBuilder);
verify(listBuilder, never()).addRow(any(ListBuilder.RowBuilder.class));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt
index c7f1525e2946..9ab5f8948ecc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryImpl
import com.android.systemui.statusbar.CommandQueue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -34,10 +35,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyEventRepositoryTest : SysuiTestCase() {
@@ -62,6 +63,15 @@ class KeyEventRepositoryTest : SysuiTestCase() {
}
@Test
+ fun isPowerButtonBeingLongPressed_initialValueFalse() =
+ testScope.runTest {
+ val isPowerButtonLongPressed by collectLastValue(
+ underTest.isPowerButtonLongPressed)
+ runCurrent()
+ assertThat(isPowerButtonLongPressed).isFalse()
+ }
+
+ @Test
fun isPowerButtonDown_onChange() =
testScope.runTest {
val isPowerButtonDown by collectLastValue(underTest.isPowerButtonDown)
@@ -77,4 +87,54 @@ class KeyEventRepositoryTest : SysuiTestCase() {
)
assertThat(isPowerButtonDown).isFalse()
}
+
+
+ @Test
+ fun isPowerButtonBeingLongPressed_onPowerButtonDown() =
+ testScope.runTest {
+ val isPowerButtonLongPressed by collectLastValue(
+ underTest.isPowerButtonLongPressed)
+
+ runCurrent()
+
+ verify(commandQueue).addCallback(commandQueueCallbacks.capture())
+
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POWER)
+ commandQueueCallbacks.value.handleSystemKey(keyEvent)
+
+ assertThat(isPowerButtonLongPressed).isFalse()
+ }
+
+ @Test
+ fun isPowerButtonBeingLongPressed_onPowerButtonUp() =
+ testScope.runTest {
+ val isPowerButtonLongPressed by collectLastValue(
+ underTest.isPowerButtonLongPressed)
+
+ runCurrent()
+
+ verify(commandQueue).addCallback(commandQueueCallbacks.capture())
+
+ val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_POWER)
+ commandQueueCallbacks.value.handleSystemKey(keyEvent)
+
+ assertThat(isPowerButtonLongPressed).isFalse()
+ }
+
+ @Test
+ fun isPowerButtonBeingLongPressed_onPowerButtonDown_longPressFlagSet() =
+ testScope.runTest {
+ val isPowerButtonBeingLongPressed by collectLastValue(
+ underTest.isPowerButtonLongPressed)
+
+ runCurrent()
+
+ verify(commandQueue).addCallback(commandQueueCallbacks.capture())
+
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POWER)
+ keyEvent.setFlags(KeyEvent.FLAG_LONG_PRESS)
+ commandQueueCallbacks.value.handleSystemKey(keyEvent)
+
+ assertThat(isPowerButtonBeingLongPressed).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 6c4325adced4..2ab36501d87d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -35,7 +35,6 @@ package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
@@ -43,8 +42,6 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.setCommunalV2Available
@@ -72,7 +69,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.runBlocking
@@ -433,9 +429,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() {
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
fun testTransitionToGlanceableHub_onWakeUpFromAod() =
kosmos.runTest {
- val user = setCommunalV2Available(true)
- fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, user.id)
- batteryRepository.fake.setDevicePluggedIn(true)
+ setCommunalV2Available(true)
val currentScene by collectLastValue(communalSceneInteractor.currentScene)
fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
@@ -449,4 +443,24 @@ class FromAodTransitionInteractorTest : SysuiTestCase() {
Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
assertThat(transitionRepository).noTransitionsStarted()
}
+
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun testDoNotTransitionToGlanceableHub_onWakeUpFromAodDueToMotion() =
+ kosmos.runTest {
+ setCommunalV2Available(true)
+
+ val currentScene by collectLastValue(communalSceneInteractor.currentScene)
+ fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
+
+ // Communal is not showing
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT)
+ testScope.advanceTimeBy(100) // account for debouncing
+
+ Truth.assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 9be786fab34d..c3d18a3d893c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -20,7 +20,6 @@ import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -30,8 +29,6 @@ import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
@@ -61,8 +58,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.flowOf
@@ -171,15 +166,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
kosmos.runTest {
setCommunalAvailable(true)
- if (glanceableHubV2()) {
- val user = fakeUserRepository.asMainUser()
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 1,
- user.id,
- )
- batteryRepository.fake.setDevicePluggedIn(true)
- } else {
+ if (!glanceableHubV2()) {
whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
}
@@ -193,6 +180,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun testTransitionToLockscreen_onWake_canNotDream_glanceableHubAvailable() =
kosmos.runTest {
whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
@@ -225,15 +213,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
kosmos.runTest {
setCommunalAvailable(true)
- if (glanceableHubV2()) {
- val user = fakeUserRepository.asMainUser()
- fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 1,
- user.id,
- )
- batteryRepository.fake.setDevicePluggedIn(true)
- } else {
+ if (!glanceableHubV2()) {
whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
}
@@ -249,6 +229,25 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun testTransitionToLockscreen_onWakeupFromLift() =
+ kosmos.runTest {
+ setCommunalAvailable(true)
+ if (!glanceableHubV2()) {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
+
+ // Device turns on.
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT)
+ testScope.advanceTimeBy(51L)
+
+ // We transition to the lockscreen instead of the hub.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN)
+ }
+
+ @Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
kosmos.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
index 2558d583b001..89a53f5722ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
@@ -49,6 +49,7 @@ import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
class KeyguardStateCallbackInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -81,7 +82,6 @@ class KeyguardStateCallbackInteractorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun test_lockscreenVisibility_notifyDismissSucceeded_ifNotVisible() =
testScope.runTest {
underTest.addCallback(callback)
@@ -109,7 +109,6 @@ class KeyguardStateCallbackInteractorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun test_lockscreenVisibility_reportsKeyguardShowingChanged() =
testScope.runTest {
underTest.addCallback(callback)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index e203a276a2f2..1dddfc1bba9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -18,11 +18,17 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
import android.view.accessibility.accessibilityManagerWrapper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.logging.uiEventLogger
+import com.android.systemui.Flags.FLAG_DOUBLE_TAP_TO_SLEEP
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
@@ -39,6 +45,8 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
+import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
@@ -46,14 +54,19 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
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.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -61,17 +74,23 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
this.uiEventLogger = mock<UiEventLoggerFake>()
}
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
private lateinit var underTest: KeyguardTouchHandlingInteractor
private val logger = kosmos.uiEventLogger
private val testScope = kosmos.testScope
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val secureSettingsRepository = kosmos.userAwareSecureSettingsRepository
+
+ @Mock private lateinit var powerManager: PowerManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true)
+ overrideResource(com.android.internal.R.bool.config_supportDoubleTapSleep, true)
whenever(kosmos.accessibilityManagerWrapper.getRecommendedTimeoutMillis(anyInt(), anyInt()))
.thenAnswer { it.arguments[0] }
@@ -80,13 +99,13 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
@After
fun tearDown() {
- mContext
- .getOrCreateTestableResources()
- .removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled)
+ val testableResource = mContext.getOrCreateTestableResources()
+ testableResource.removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled)
+ testableResource.removeOverride(com.android.internal.R.bool.config_supportDoubleTapSleep)
}
@Test
- fun isEnabled() =
+ fun isLongPressEnabled() =
testScope.runTest {
val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
KeyguardState.values().forEach { keyguardState ->
@@ -101,7 +120,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
}
@Test
- fun isEnabled_alwaysFalseWhenQuickSettingsAreVisible() =
+ fun isLongPressEnabled_alwaysFalseWhenQuickSettingsAreVisible() =
testScope.runTest {
val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
KeyguardState.values().forEach { keyguardState ->
@@ -112,7 +131,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
}
@Test
- fun isEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() =
+ fun isLongPressEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() =
testScope.runTest {
overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, false)
createUnderTest()
@@ -294,6 +313,119 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
assertThat(isMenuVisible).isFalse()
}
+ @Test
+ @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun isDoubleTapEnabled_flagEnabled_userSettingEnabled_onlyTrueInLockScreenState() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true)
+
+ val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ KeyguardState.entries.forEach { keyguardState ->
+ setUpState(keyguardState = keyguardState)
+
+ if (keyguardState == KeyguardState.LOCKSCREEN) {
+ assertThat(isEnabled()).isTrue()
+ } else {
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun isDoubleTapEnabled_flagEnabled_userSettingDisabled_alwaysFalse() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, false)
+
+ val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ KeyguardState.entries.forEach { keyguardState ->
+ setUpState(keyguardState = keyguardState)
+
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun isDoubleTapEnabled_flagDisabled_userSettingEnabled_alwaysFalse() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true)
+
+ val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ KeyguardState.entries.forEach { keyguardState ->
+ setUpState(keyguardState = keyguardState)
+
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+ }
+
+
+
+ @Test
+ @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun isDoubleTapEnabled_flagEnabledAndConfigDisabled_alwaysFalse() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true)
+ overrideResource(com.android.internal.R.bool.config_supportDoubleTapSleep, false)
+ createUnderTest()
+
+ val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ KeyguardState.entries.forEach { keyguardState ->
+ setUpState(keyguardState = keyguardState)
+
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun isDoubleTapEnabled_quickSettingsVisible_alwaysFalse() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true)
+
+ val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ KeyguardState.entries.forEach { keyguardState ->
+ setUpState(keyguardState = keyguardState, isQuickSettingsVisible = true)
+
+ assertThat(isEnabled()).isFalse()
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun onDoubleClick_doubleTapEnabled() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true)
+ val isEnabled by collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ runCurrent()
+
+ underTest.onDoubleClick()
+
+ assertThat(isEnabled).isTrue()
+ verify(powerManager).goToSleep(anyLong())
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP)
+ fun onDoubleClick_doubleTapDisabled() {
+ testScope.runTest {
+ secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, false)
+ val isEnabled by collectLastValue(underTest.isDoubleTapHandlingEnabled)
+ runCurrent()
+
+ underTest.onDoubleClick()
+
+ assertThat(isEnabled).isFalse()
+ verify(powerManager, never()).goToSleep(anyLong())
+ }
+ }
+
private suspend fun createUnderTest(isRevampedWppFeatureEnabled: Boolean = true) {
// This needs to be re-created for each test outside of kosmos since the flag values are
// read during initialization to set up flows. Maybe there is a better way to handle that.
@@ -309,6 +441,9 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
accessibilityManager = kosmos.accessibilityManagerWrapper,
pulsingGestureListener = kosmos.pulsingGestureListener,
faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ secureSettingsRepository = secureSettingsRepository,
+ powerManager = powerManager,
+ systemClock = kosmos.fakeSystemClock,
)
setUpState()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 8df70ef0fd2e..7d5e9a5ed178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -33,7 +33,7 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
-import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2Available
import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -1004,16 +1004,15 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
@BrokenWithSceneContainer(339465026)
fun occludedToGlanceableHub_communalKtfRefactor() =
testScope.runTest {
- // GIVEN a device on lockscreen and communal is available
- keyguardRepository.setKeyguardShowing(true)
- kosmos.setCommunalAvailable(true)
- runCurrent()
-
// GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
+ // GIVEN a device on lockscreen and communal is available
+ kosmos.setCommunalV2Available(true)
+ runCurrent()
+
// WHEN occlusion ends
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 97c746c49cba..d0762a3797c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -25,10 +25,13 @@ import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationTarget
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.android.window.flags.Flags
@@ -63,6 +66,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
@Mock
private lateinit var keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor
@Mock private lateinit var keyguardTransitions: KeyguardTransitions
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Before
fun setUp() {
@@ -77,6 +83,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
keyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor,
keyguardTransitions = keyguardTransitions,
+ selectedUserInteractor = selectedUserInteractor,
+ lockPatternUtils = lockPatternUtils,
+ keyguardShowWhileAwakeInteractor = keyguardShowWhileAwakeInteractor,
)
}
@@ -236,6 +245,8 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
.whenever(keyguardDismissTransitionInteractor)
.startDismissKeyguardTransition(any(), any())
+ whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(-1)
+
underTest.onKeyguardGoingAwayRemoteAnimationStart(
transit = 0,
apps = arrayOf(mock<RemoteAnimationTarget>()),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 83bee7c66d31..fe213a6ebbf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -458,6 +458,56 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
@Test
@DisableSceneContainer
+ fun alpha_shadeExpansionIgnoredWhenTransitioningAwayFromLockscreen() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ shadeTestUtil.setQsExpansion(0f)
+ assertThat(alpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ ),
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.8f,
+ ),
+ ),
+ testScope,
+ )
+ val priorAlpha = alpha
+ shadeTestUtil.setQsExpansion(0.5f)
+ assertThat(alpha).isEqualTo(priorAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ ),
+ testScope,
+ )
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ @DisableSceneContainer
fun alphaFromShadeExpansion_doesNotEmitWhenTransitionRunning() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 91cb1ff266c9..9c168298b9a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -44,7 +45,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runCurrent
@@ -101,20 +102,30 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
// immediately 0f
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(.2f))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(0.8f))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun lockscreenAlphaEndsWithZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+
+ // Jump right to the end and validate the value
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(alpha).isEqualTo(0f)
}
@Test
@@ -138,21 +149,17 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
runCurrent()
// fade out
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(1f)
+ assertThat(actual).isEqualTo(1f)
repository.sendTransitionStep(step(.1f))
- runCurrent()
- Truth.assertThat(actual).isIn(Range.open(.1f, .9f))
+ assertThat(actual).isIn(Range.open(.1f, .9f))
// alpha is 1f before the full transition starts ending
repository.sendTransitionStep(step(0.8f))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- runCurrent()
- Truth.assertThat(actual).isEqualTo(0f)
+ assertThat(actual).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt
index 8533134fd94e..95a6e56717fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt
@@ -21,15 +21,20 @@ import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -43,6 +48,7 @@ class OccludedToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterizati
SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val repository = kosmos.fakeKeyguardTransitionRepository
private lateinit var underTest: OccludedToPrimaryBouncerTransitionViewModel
companion object {
@@ -63,6 +69,25 @@ class OccludedToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterizati
}
@Test
+ @DisableSceneContainer
+ fun lockscreenAlphaImmediatelyToZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING))
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
@BrokenWithSceneContainer(388068805)
fun notificationsAreBlurredImmediatelyWhenBouncerIsOpenedAndShadeIsExpanded() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt
new file mode 100644
index 000000000000..d5d256e5cd97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class LogWtfHandlerRuleTest : SysuiTestCase() {
+
+ val underTest = LogWtfHandlerRule()
+
+ @Test
+ fun passingTestWithoutWtf_shouldPass() {
+ val result = runTestCodeWithRule {
+ Log.e(TAG, "just an error", IndexOutOfBoundsException())
+ }
+ assertThat(result.isSuccess).isTrue()
+ }
+
+ @Test
+ fun passingTestWithWtf_shouldFail() {
+ val result = runTestCodeWithRule {
+ Log.wtf(TAG, "some terrible failure", IllegalStateException())
+ }
+ assertThat(result.isFailure).isTrue()
+ val exception = result.exceptionOrNull()
+ assertThat(exception).isInstanceOf(AssertionError::class.java)
+ assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java)
+ assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java)
+ }
+
+ @Test
+ fun failingTestWithoutWtf_shouldFail() {
+ val result = runTestCodeWithRule {
+ Log.e(TAG, "just an error", IndexOutOfBoundsException())
+ throw NullPointerException("some npe")
+ }
+ assertThat(result.isFailure).isTrue()
+ assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java)
+ }
+
+ @Test
+ fun failingTestWithWtf_shouldFail() {
+ val result = runTestCodeWithRule {
+ Log.wtf(TAG, "some terrible failure", IllegalStateException())
+ throw NullPointerException("some npe")
+ }
+ assertThat(result.isFailure).isTrue()
+ assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java)
+ val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions
+ assertThat(suppressedExceptions).hasSize(1)
+ val suppressed = suppressedExceptions.first()
+ assertThat(suppressed).isInstanceOf(AssertionError::class.java)
+ assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java)
+ assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java)
+ }
+
+ @Test
+ fun passingTestWithExemptWtf_shouldPass() {
+ underTest.addFailureLogExemption { it.tag == TAG_EXPECTED }
+ val result = runTestCodeWithRule {
+ Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException())
+ }
+ assertThat(result.isSuccess).isTrue()
+ }
+
+ @Test
+ fun failingTestWithExemptWtf_shouldFail() {
+ underTest.addFailureLogExemption { it.tag == TAG_EXPECTED }
+ val result = runTestCodeWithRule {
+ Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException())
+ throw NullPointerException("some npe")
+ }
+ assertThat(result.isFailure).isTrue()
+ assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java)
+ val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions
+ assertThat(suppressedExceptions).isEmpty()
+ }
+
+ @Test
+ fun passingTestWithOneExemptWtfOfTwo_shouldFail() {
+ underTest.addFailureLogExemption { it.tag == TAG_EXPECTED }
+ val result = runTestCodeWithRule {
+ Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException())
+ Log.wtf(TAG, "some terrible failure", IllegalStateException())
+ }
+ assertThat(result.isFailure).isTrue()
+ val exception = result.exceptionOrNull()
+ assertThat(exception).isInstanceOf(AssertionError::class.java)
+ assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java)
+ assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java)
+ }
+
+ @Test
+ fun failingTestWithOneExemptWtfOfTwo_shouldFail() {
+ underTest.addFailureLogExemption { it.tag == TAG_EXPECTED }
+ val result = runTestCodeWithRule {
+ Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException())
+ Log.wtf(TAG, "some terrible failure", IllegalStateException())
+ throw NullPointerException("some npe")
+ }
+ assertThat(result.isFailure).isTrue()
+ assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java)
+ val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions
+ assertThat(suppressedExceptions).hasSize(1)
+ val suppressed = suppressedExceptions.first()
+ assertThat(suppressed).isInstanceOf(AssertionError::class.java)
+ assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java)
+ assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java)
+ }
+
+ private fun runTestCodeWithRule(testCode: () -> Unit): Result<Unit> {
+ val testCodeStatement =
+ object : Statement() {
+ override fun evaluate() {
+ testCode()
+ }
+ }
+ val wrappedTest = underTest.apply(testCodeStatement, mock())
+ return try {
+ wrappedTest.evaluate()
+ Result.success(Unit)
+ } catch (e: Throwable) {
+ Result.failure(e)
+ }
+ }
+
+ companion object {
+ const val TAG = "LogWtfHandlerRuleTest"
+ const val TAG_EXPECTED = "EXPECTED"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/NotificationMediaManagerTest.kt
index fed613153a4e..10b00857e887 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/NotificationMediaManagerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar
+package com.android.systemui.media
import android.media.MediaMetadata
import android.media.session.MediaController
@@ -125,8 +125,7 @@ class NotificationMediaManagerTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION)
- fun clearMediaNotification_flagOn_resetMediaMetadata() {
+ fun clearMediaNotification_resetMediaMetadata() {
// set up media metadata.
notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build())
backgroundExecutor.runAllReady()
@@ -138,17 +137,4 @@ class NotificationMediaManagerTest : SysuiTestCase() {
assertThat(notificationMediaManager.mediaMetadata).isNull()
assertThat(notificationMediaManager.mMediaController).isNull()
}
-
- @Test
- @DisableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION)
- fun clearMediaNotification_flagOff_resetMediaMetadata() {
- // set up media metadata.
- notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build())
-
- // clear media notification.
- notificationMediaManager.clearCurrentMediaNotification()
-
- assertThat(notificationMediaManager.mediaMetadata).isNull()
- assertThat(notificationMediaManager.mMediaController).isNull()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index 0197a1e61801..c72afc72fa16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -16,26 +16,17 @@
package com.android.systemui.media.controls.domain.interactor
-import android.R
-import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
-import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -52,16 +43,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
private val mediaFilterRepository: MediaFilterRepository =
with(kosmos) { mediaFilterRepository }
- private val mediaRecommendationsInteractor: MediaRecommendationsInteractor =
- kosmos.mediaRecommendationsInteractor
- val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- private val mediaRecommendation =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
@@ -119,81 +100,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
}
@Test
- fun addActiveRecommendation_inactiveMedia() =
- testScope.runTest {
- val hasActiveMediaOrRecommendation by
- collectLastValue(underTest.hasActiveMediaOrRecommendation)
- val hasAnyMediaOrRecommendation by
- collectLastValue(underTest.hasAnyMediaOrRecommendation)
- val currentMedia by collectLastValue(underTest.currentMedia)
-
- val userMedia = MediaData(active = false)
- val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
- val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
- mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel)
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
- assertThat(currentMedia)
- .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel))
-
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
- mediaFilterRepository.setOrderedMedia()
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
- assertThat(currentMedia)
- .containsExactly(
- MediaCommonModel.MediaRecommendations(recsLoadingModel),
- MediaCommonModel.MediaControl(mediaLoadingModel, true),
- )
- .inOrder()
- }
-
- @Test
- fun addActiveRecommendation_thenInactive() =
- testScope.runTest {
- val hasActiveMediaOrRecommendation by
- collectLastValue(underTest.hasActiveMediaOrRecommendation)
- val hasAnyMediaOrRecommendation by
- collectLastValue(underTest.hasAnyMediaOrRecommendation)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
-
- mediaFilterRepository.setRecommendation(mediaRecommendation.copy(isActive = false))
-
- assertThat(hasActiveMediaOrRecommendation).isFalse()
- assertThat(hasAnyMediaOrRecommendation).isFalse()
- }
-
- @Test
- fun addActiveRecommendation_thenInvalid() =
- testScope.runTest {
- val hasActiveMediaOrRecommendation by
- collectLastValue(underTest.hasActiveMediaOrRecommendation)
- val hasAnyMediaOrRecommendation by
- collectLastValue(underTest.hasAnyMediaOrRecommendation)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
-
- mediaFilterRepository.setRecommendation(
- mediaRecommendation.copy(recommendations = listOf())
- )
-
- assertThat(hasActiveMediaOrRecommendation).isFalse()
- assertThat(hasAnyMediaOrRecommendation).isFalse()
- }
-
- @Test
fun hasAnyMedia_noMediaSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasAnyMedia()).isFalse() }
@@ -208,47 +114,4 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() }
-
- @Test
- fun loadMediaFromRec() =
- testScope.runTest {
- val currentMedia by collectLastValue(underTest.currentMedia)
- val instanceId = InstanceId.fakeInstanceId(123)
- val data =
- MediaData(
- active = true,
- instanceId = instanceId,
- packageName = PACKAGE_NAME,
- notificationKey = KEY,
- )
- val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
- val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
- mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel)
- mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
- mediaFilterRepository.addSelectedUserMediaEntry(data)
- mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
-
- assertThat(currentMedia)
- .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel))
- .inOrder()
-
- mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true))
- mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
-
- assertThat(currentMedia)
- .containsExactly(
- MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true),
- MediaCommonModel.MediaRecommendations(smartspaceLoadingModel),
- )
- .inOrder()
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val PACKAGE_NAME = "com.android.example"
- private const val KEY = "key"
- private const val SURFACE = 4
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
deleted file mode 100644
index 2265c0149cc3..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor
-
-import android.R
-import android.content.ComponentName
-import android.content.Intent
-import android.content.applicationContext
-import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Expandable
-import com.android.systemui.broadcast.broadcastSender
-import com.android.systemui.broadcast.mockBroadcastSender
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
-import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor.Companion.EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
-import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
-import com.android.systemui.media.controls.shared.model.MediaRecModel
-import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.kotlin.eq
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MediaRecommendationsInteractorTest : SysuiTestCase() {
-
- private val spyContext = spy(context)
- private val kosmos = testKosmos().apply { applicationContext = spyContext }
- private val testScope = kosmos.testScope
-
- private val mediaDataFilter: MediaDataFilterImpl = with(kosmos) { mediaDataFilter }
- private val activityStarter = kosmos.activityStarter
- private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- private val smartspaceMediaData: SmartspaceMediaData =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
-
- private val underTest: MediaRecommendationsInteractor =
- with(kosmos) {
- broadcastSender = mockBroadcastSender
- kosmos.mediaRecommendationsInteractor
- }
-
- @Test
- fun addRecommendation_smartspaceMediaDataUpdate() =
- testScope.runTest {
- val recommendations by collectLastValue(underTest.recommendations)
-
- val model =
- MediaRecommendationsModel(
- key = KEY_MEDIA_SMARTSPACE,
- packageName = PACKAGE_NAME,
- areRecommendationsValid = true,
- mediaRecs =
- listOf(
- MediaRecModel(icon = icon),
- MediaRecModel(icon = icon),
- MediaRecModel(icon = icon),
- ),
- )
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
-
- assertThat(recommendations).isEqualTo(model)
- }
-
- @Test
- fun addInvalidRecommendation() =
- testScope.runTest {
- val recommendations by collectLastValue(underTest.recommendations)
- val inValidData = smartspaceMediaData.copy(recommendations = listOf())
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
- assertThat(recommendations?.areRecommendationsValid).isTrue()
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, inValidData)
- assertThat(recommendations?.areRecommendationsValid).isFalse()
- assertThat(recommendations?.mediaRecs?.isEmpty()).isTrue()
- }
-
- @Test
- fun removeRecommendation_noTrampolineActivity() {
- val intent = Intent()
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
- underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
-
- verify(kosmos.mockBroadcastSender).sendBroadcast(eq(intent))
- }
-
- @Test
- fun removeRecommendation_usingTrampolineActivity() {
- doNothing().whenever(spyContext).startActivity(any())
- val intent = Intent()
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- intent.component = ComponentName(PACKAGE_NAME, EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)
-
- underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
-
- verify(spyContext).startActivity(eq(intent))
- }
-
- @Test
- fun startSettings() {
- underTest.startSettings()
-
- verify(activityStarter).startActivity(any(), eq(true))
- }
-
- @Test
- fun startClickIntent() {
- doNothing().whenever(spyContext).startActivity(any())
- val intent = Intent()
- val expandable = mock<Expandable>()
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
- underTest.startClickIntent(expandable, intent)
-
- verify(spyContext).startActivity(eq(intent))
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val PACKAGE_NAME = "com.example.app"
- private const val SURFACE = 4
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
index 005424ba599e..faa62c2febc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
@@ -23,7 +23,6 @@ import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
import com.android.systemui.media.controls.ui.viewmodel.mediaControlViewModel
-import com.android.systemui.media.controls.ui.viewmodel.mediaRecommendationsViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
@@ -56,25 +55,6 @@ class MediaDiffUtilTest : SysuiTestCase() {
}
@Test
- fun newMediaRecommendationsAdded() {
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
- val oldList = listOf<MediaCommonViewModel>()
- val newList = listOf(mediaRecs)
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
- @Test
fun updateMediaControl_contentChanged() {
val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
val oldList = listOf(mediaControl)
@@ -94,25 +74,6 @@ class MediaDiffUtilTest : SysuiTestCase() {
}
@Test
- fun updateMediaRecommendations_contentChanged() {
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
- val oldList = listOf(mediaRecs)
- val newList = listOf(mediaRecs.copy(key = KEY_MEDIA_SMARTSPACE_2))
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> assertThat(commonViewModel).isNotEqualTo(mediaRecs) },
- { fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
- @Test
fun mediaControlMoved() {
val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true)
val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false)
@@ -133,27 +94,6 @@ class MediaDiffUtilTest : SysuiTestCase() {
}
@Test
- fun mediaRecommendationsMoved() {
- val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true)
- val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false)
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
- val oldList = listOf(mediaRecs, mediaControl1, mediaControl2)
- val newList = listOf(mediaControl1, mediaControl2, mediaRecs)
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
- @Test
fun mediaControlRemoved() {
val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
val oldList = listOf(mediaControl)
@@ -172,25 +112,6 @@ class MediaDiffUtilTest : SysuiTestCase() {
DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
}
- @Test
- fun mediaRecommendationsRemoved() {
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE_2, false)
- val oldList = listOf(mediaRecs)
- val newList = listOf<MediaCommonViewModel>()
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
private fun createMediaControl(
instanceId: InstanceId,
immediatelyUpdateUi: Boolean,
@@ -201,26 +122,7 @@ class MediaDiffUtilTest : SysuiTestCase() {
controlViewModel = kosmos.mediaControlViewModel,
onAdded = {},
onRemoved = {},
- onUpdated = {}
- )
- }
-
- private fun createMediaRecommendations(
- key: String,
- loadingEnabled: Boolean,
- ): MediaCommonViewModel.MediaRecommendations {
- return MediaCommonViewModel.MediaRecommendations(
- key = key,
- loadingEnabled = loadingEnabled,
- recsViewModel = kosmos.mediaRecommendationsViewModel,
- onAdded = {},
- onRemoved = {},
- onUpdated = {}
+ onUpdated = {},
)
}
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val KEY_MEDIA_SMARTSPACE_2 = "MEDIA_SMARTSPACE_ID_2"
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index 61119cce7bc8..8592c424ca02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -235,6 +235,19 @@ class MediaCarouselScrollHandlerTest : SysuiTestCase() {
verify(mediaCarousel, never()).animationTargetX = anyFloat()
}
+ @Test
+ fun testScrollingDisabled_noScroll_notDismissible() {
+ setupMediaContainer(visibleIndex = 1, showsSettingsButton = false)
+
+ mediaCarouselScrollHandler.scrollingDisabled = true
+
+ clock.advanceTime(DISMISS_DELAY)
+ executor.runAllReady()
+
+ verify(mediaCarousel, never()).smoothScrollTo(anyInt(), anyInt())
+ verify(mediaCarousel, never()).animationTargetX = anyFloat()
+ }
+
private fun setupMediaContainer(visibleIndex: Int, showsSettingsButton: Boolean = true) {
whenever(contentContainer.childCount).thenReturn(2)
val child1: View = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index fb5bbf452cfa..e56b114dc847 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -19,23 +19,18 @@ package com.android.systemui.media.controls.ui.viewmodel
import android.R
import android.content.packageManager
import android.content.pm.ApplicationInfo
-import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -62,15 +57,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val packageManager = kosmos.packageManager
- private val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
private val drawable = context.getDrawable(R.drawable.ic_media_play)
- private val smartspaceMediaData: SmartspaceMediaData =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
private val underTest: MediaCarouselViewModel = kosmos.mediaCarouselViewModel
@@ -121,53 +108,6 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
}
@Test
- fun loadMediaControlsAndRecommendations_mediaItemsAreUpdated() =
- testScope.runTest {
- val sortedMedia by collectLastValue(underTest.mediaItems)
- val instanceId1 = InstanceId.fakeInstanceId(123)
- val instanceId2 = InstanceId.fakeInstanceId(456)
-
- loadMediaControl(KEY, instanceId1)
- loadMediaControl(KEY_2, instanceId2)
- loadMediaRecommendations()
-
- val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
- val recsCard = sortedMedia?.get(2) as MediaCommonViewModel.MediaRecommendations
- assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2)
- assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1)
- assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
- }
-
- @Test
- fun recommendationClicked_switchToPlayer() =
- testScope.runTest {
- val sortedMedia by collectLastValue(underTest.mediaItems)
- kosmos.visualStabilityProvider.isReorderingAllowed = false
- val instanceId = InstanceId.fakeInstanceId(123)
-
- loadMediaRecommendations()
- kosmos.mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
-
- var recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
- assertThat(sortedMedia).hasSize(1)
- assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
-
- loadMediaControl(KEY, instanceId, false)
-
- recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
- assertThat(sortedMedia).hasSize(1)
- assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
-
- loadMediaControl(KEY, instanceId, true)
-
- val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- assertThat(sortedMedia).hasSize(2)
- assertThat(mediaControl.instanceId).isEqualTo(instanceId)
- assertThat(mediaControl.isMediaFromRec).isTrue()
- }
-
- @Test
fun addMediaControlThenRemove_mediaEventsAreLogged() =
testScope.runTest {
val sortedMedia by collectLastValue(underTest.mediaItems)
@@ -199,31 +139,6 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
verify(kosmos.mediaLogger).logMediaCardRemoved(eq(instanceId))
}
- @Test
- fun addMediaRecommendationThenRemove_mediaEventsAreLogged() =
- testScope.runTest {
- val sortedMedia by collectLastValue(underTest.mediaItems)
-
- loadMediaRecommendations()
-
- val mediaRecommendations =
- sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
- assertThat(mediaRecommendations.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
-
- // when media recommendation is added to carousel
- mediaRecommendations.onAdded(mediaRecommendations)
-
- verify(kosmos.mediaLogger).logMediaRecommendationCardAdded(eq(KEY_MEDIA_SMARTSPACE))
-
- mediaDataFilter.onSmartspaceMediaDataRemoved(KEY, true)
- assertThat(sortedMedia).isEmpty()
-
- // when media recommendation is removed from carousel
- mediaRecommendations.onRemoved(true)
-
- verify(kosmos.mediaLogger).logMediaRecommendationCardRemoved(eq(KEY_MEDIA_SMARTSPACE))
- }
-
private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) {
whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
@@ -239,15 +154,10 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
mediaDataFilter.onMediaDataLoaded(key, key, mediaData)
}
- private fun loadMediaRecommendations(key: String = KEY_MEDIA_SMARTSPACE) {
- mediaDataFilter.onSmartspaceMediaDataLoaded(key, smartspaceMediaData)
- }
-
companion object {
private const val USER_ID = 0
private const val KEY = "key"
private const val KEY_2 = "key2"
private const val PACKAGE_NAME = "com.example.app"
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt
deleted file mode 100644
index 51b1911be5d5..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
-
-import android.R
-import android.content.packageManager
-import android.content.pm.ApplicationInfo
-import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
-import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
-import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MediaRecommendationsViewModelTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
- private val packageManager = kosmos.packageManager
- private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- private val drawable = context.getDrawable(R.drawable.ic_media_play)
- private val smartspaceMediaData: SmartspaceMediaData =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
-
- private val underTest: MediaRecommendationsViewModel = kosmos.mediaRecommendationsViewModel
-
- @Test
- fun loadRecommendations_recsCardViewModelIsLoaded() =
- testScope.runTest {
- whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
- whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
- .thenReturn(drawable)
- whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
- .thenReturn(ApplicationInfo())
- whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
- val recsCardViewModel by collectLastValue(underTest.mediaRecsCard)
-
- context.setMockPackageManager(packageManager)
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
-
- assertThat(recsCardViewModel).isNotNull()
- assertThat(recsCardViewModel?.mediaRecs?.size)
- .isEqualTo(smartspaceMediaData.recommendations.size)
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val PACKAGE_NAME = "com.example.app"
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java
index 2db2199602b8..9c4d93c17d00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java
@@ -48,10 +48,12 @@ import androidx.test.filters.SmallTest;
import com.android.media.flags.Flags;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListeningExecutorService;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +63,7 @@ import org.mockito.Captor;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
@SmallTest
@@ -95,6 +98,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private List<MediaItem> mMediaItems = new ArrayList<>();
MediaOutputSeekbar mSpyMediaOutputSeekbar;
+ Executor mMainExecutor = mContext.getMainExecutor();
+ ListeningExecutorService mBackgroundExecutor = ThreadUtils.getBackgroundExecutor();
@Before
public void setUp() {
@@ -108,6 +113,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
when(mMediaSwitchingController.getSessionVolumeMax()).thenReturn(TEST_MAX_VOLUME);
when(mMediaSwitchingController.getSessionVolume()).thenReturn(TEST_CURRENT_VOLUME);
when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+ when(mMediaSwitchingController.getColorSchemeLegacy()).thenReturn(
+ mock(MediaOutputColorSchemeLegacy.class));
when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -122,7 +129,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1, true));
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2, false));
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -148,7 +156,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindPairNew_verifyView() {
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -173,7 +182,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
.map((item) -> item.getMediaDevice().get())
.collect(Collectors.toList()));
when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -195,7 +205,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
.map((item) -> item.getMediaDevice().get())
.collect(Collectors.toList()));
when(mMediaSwitchingController.getSessionName()).thenReturn(null);
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -665,7 +676,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
@Test
public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -683,7 +695,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -701,7 +714,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController,
+ mContext.getMainExecutor(), ThreadUtils.getBackgroundExecutor());
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -723,7 +737,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
when(mMediaDevice2.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
- mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor,
+ mBackgroundExecutor);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -778,6 +793,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mTitleText.getAlpha())
+ .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
mViewHolder.mContainerLayout.performClick();
@@ -799,6 +816,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mTitleText.getAlpha())
+ .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
mViewHolder.mContainerLayout.performClick();
@@ -820,6 +839,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mTitleText.getAlpha())
+ .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
mViewHolder.mContainerLayout.performClick();
@@ -841,6 +862,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mTitleText.getAlpha())
+ .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
mViewHolder.mContainerLayout.performClick();
@@ -862,6 +885,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mTitleText.getAlpha())
+ .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
mViewHolder.mContainerLayout.performClick();
@@ -883,6 +908,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase {
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mTitleText.getAlpha())
+ .isEqualTo(MediaOutputAdapterLegacy.DEVICE_DISABLED_ALPHA);
assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
mViewHolder.mContainerLayout.performClick();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt
new file mode 100644
index 000000000000..b2e29cf60c27
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class SceneContainerPluginTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val shadeDisplayRepository = kosmos.fakeShadeDisplaysRepository
+ private val sceneDataSource = kosmos.fakeSceneDataSource
+
+ private val underTest = kosmos.sceneContainerPlugin
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_differentDisplayId_alwaysFalse() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 2,
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_sameDisplayId_returnsTrue() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 1,
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_differentDisplayId_shadeGoesAroundFlagOff_returnsTrue() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 2,
+ )
+ )
+ .isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
new file mode 100644
index 000000000000..ff2e13b51e14
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStateDispatcherTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val stateFactory = kosmos.sysUiStateFactory
+ private val state0 = stateFactory.create(Display.DEFAULT_DISPLAY)
+ private val state1 = stateFactory.create(DISPLAY_1)
+ private val state2 = stateFactory.create(DISPLAY_2)
+ private val underTest = kosmos.sysUIStateDispatcher
+
+ private val flagsChanges = mutableMapOf<Int, Long>() // display id -> flag value
+ private val callback =
+ SysUiState.SysUiStateCallback { sysUiFlags, displayId ->
+ flagsChanges[displayId] = sysUiFlags
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun registerUnregisterListener_notifiedOfChanges_receivedForAllDisplayIdsWithOneCallback() {
+ underTest.registerListener(callback)
+
+ state1.setFlag(FLAG_1, true).commitUpdate()
+ state2.setFlag(FLAG_2, true).commitUpdate()
+
+ assertThat(flagsChanges).containsExactly(DISPLAY_1, FLAG_1, DISPLAY_2, FLAG_2)
+
+ underTest.unregisterListener(callback)
+
+ state1.setFlag(0, true).commitUpdate()
+
+ // Didn't change
+ assertThat(flagsChanges).containsExactly(DISPLAY_1, FLAG_1, DISPLAY_2, FLAG_2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun registerUnregisterListener_notifiedOfChangesForNonDefaultDisplay_NotPropagated() {
+ underTest.registerListener(callback)
+
+ state1.setFlag(FLAG_1, true).commitUpdate()
+
+ assertThat(flagsChanges).isEmpty()
+
+ state0.setFlag(FLAG_1, true).commitUpdate()
+
+ assertThat(flagsChanges).containsExactly(Display.DEFAULT_DISPLAY, FLAG_1)
+ }
+
+ private companion object {
+ const val DISPLAY_1 = 1
+ const val DISPLAY_2 = 2
+ const val FLAG_1 = 1L
+ const val FLAG_2 = 2L
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt
new file mode 100644
index 000000000000..24bd8adaa45a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateOverrideTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.never
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStateOverrideTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val defaultState = kosmos.sysUiState
+ private val callbackOnOverride = mock<SysUiState.SysUiStateCallback>()
+ private val dumpManager = kosmos.dumpManager
+
+ private val underTest = kosmos.sysUiStateOverrideFactory.invoke(DISPLAY_1)
+
+ @Before
+ fun setup() {
+ underTest.start()
+ underTest.addCallback(callbackOnOverride)
+ reset(callbackOnOverride)
+ }
+
+ @Test
+ fun setFlag_setOnDefaultState_propagatedToOverride() {
+ defaultState.setFlag(FLAG_1, true).commitUpdate()
+
+ verify(callbackOnOverride).onSystemUiStateChanged(FLAG_1, Display.DEFAULT_DISPLAY)
+ verify(callbackOnOverride).onSystemUiStateChanged(FLAG_1, DISPLAY_1)
+ }
+
+ @Test
+ fun setFlag_onOverride_overridesDefaultOnes() {
+ defaultState.setFlag(FLAG_1, false).setFlag(FLAG_2, true).commitUpdate()
+ underTest.setFlag(FLAG_1, true).setFlag(FLAG_2, false).commitUpdate()
+
+ assertThat(underTest.isFlagEnabled(FLAG_1)).isTrue()
+ assertThat(underTest.isFlagEnabled(FLAG_2)).isFalse()
+
+ assertThat(defaultState.isFlagEnabled(FLAG_1)).isFalse()
+ assertThat(defaultState.isFlagEnabled(FLAG_2)).isTrue()
+ }
+
+ @Test
+ fun destroy_callbacksForDefaultStateNotReceivedAnymore() {
+ defaultState.setFlag(FLAG_1, true).commitUpdate()
+
+ verify(callbackOnOverride).onSystemUiStateChanged(FLAG_1, Display.DEFAULT_DISPLAY)
+
+ reset(callbackOnOverride)
+ underTest.destroy()
+ defaultState.setFlag(FLAG_1, false).commitUpdate()
+
+ verify(callbackOnOverride, never()).onSystemUiStateChanged(FLAG_1, Display.DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun init_registersWithDumpManager() {
+ verify(dumpManager).registerNormalDumpable(any(), eq(underTest))
+ }
+
+ @Test
+ fun destroy_unregistersWithDumpManager() {
+ underTest.destroy()
+
+ verify(dumpManager).unregisterDumpable(ArgumentMatchers.anyString())
+ }
+
+ private companion object {
+ const val DISPLAY_1 = 1
+ const val FLAG_1 = 1L
+ const val FLAG_2 = 2L
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
index a3be9e35b912..d1b552906fbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -20,7 +20,6 @@ import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -32,23 +31,14 @@ class SysUiStateExtTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val underTest =
- SysUiState(
- FakeDisplayTracker(context),
- kosmos.sceneContainerPlugin,
- )
+ private val underTest = kosmos.sysUiState
@Test
fun updateFlags() {
- underTest.updateFlags(
- Display.DEFAULT_DISPLAY,
- 1L to true,
- 2L to false,
- 3L to true,
- )
+ underTest.updateFlags(Display.DEFAULT_DISPLAY, 1L to true, 2L to false, 4L to true)
assertThat(underTest.flags and 1L).isNotEqualTo(0L)
assertThat(underTest.flags and 2L).isEqualTo(0L)
- assertThat(underTest.flags and 3L).isNotEqualTo(0L)
+ assertThat(underTest.flags and 4L).isNotEqualTo(0L)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
index 9a78bd93f424..5bb7f36e4187 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
@@ -19,16 +19,23 @@ package com.android.systemui.model;
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
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 android.view.Display;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.settings.FakeDisplayTracker;
import org.junit.Before;
import org.junit.Test;
@@ -46,21 +53,33 @@ public class SysUiStateTest extends SysuiTestCase {
private KosmosJavaAdapter mKosmos;
private SysUiState.SysUiStateCallback mCallback;
private SysUiState mFlagsContainer;
+ private SceneContainerPlugin mSceneContainerPlugin;
+ private DumpManager mDumpManager;
+ private SysUIStateDispatcher mSysUIStateDispatcher;
+
+ private SysUiState createInstance(int displayId) {
+ var sysuiState = new SysUiStateImpl(displayId, mSceneContainerPlugin, mDumpManager,
+ mSysUIStateDispatcher);
+ sysuiState.addCallback(mCallback);
+ return sysuiState;
+ }
@Before
public void setup() {
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
mKosmos = new KosmosJavaAdapter(this);
- mFlagsContainer = new SysUiState(displayTracker, mKosmos.getSceneContainerPlugin());
+ mFlagsContainer = mKosmos.getSysuiState();
+ mSceneContainerPlugin = mKosmos.getSceneContainerPlugin();
mCallback = mock(SysUiState.SysUiStateCallback.class);
- mFlagsContainer.addCallback(mCallback);
+ mDumpManager = mock(DumpManager.class);
+ mSysUIStateDispatcher = mKosmos.getSysUIStateDispatcher();
+ mFlagsContainer = createInstance(DEFAULT_DISPLAY);
}
@Test
public void addSingle_setFlag() {
setFlags(FLAG_1);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
}
@Test
@@ -68,22 +87,19 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_1);
setFlags(FLAG_2);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
- verify(mCallback, times(1))
- .onSystemUiStateChanged(FLAG_1 | FLAG_2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2, DEFAULT_DISPLAY);
}
@Test
public void addMultipleRemoveOne_setFlag() {
setFlags(FLAG_1);
setFlags(FLAG_2);
- mFlagsContainer.setFlag(FLAG_1, false)
- .commitUpdate(DISPLAY_ID);
+ mFlagsContainer.setFlag(FLAG_1, false).commitUpdate(DISPLAY_ID);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
- verify(mCallback, times(1))
- .onSystemUiStateChanged(FLAG_1 | FLAG_2);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_2, DEFAULT_DISPLAY);
}
@Test
@@ -91,19 +107,18 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
int expected = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected, DEFAULT_DISPLAY);
}
@Test
public void addMultipleRemoveOne_setFlags() {
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
- mFlagsContainer.setFlag(FLAG_2, false)
- .commitUpdate(DISPLAY_ID);
+ mFlagsContainer.setFlag(FLAG_2, false).commitUpdate(DISPLAY_ID);
int expected1 = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected1);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected1, DEFAULT_DISPLAY);
int expected2 = FLAG_1 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected2, DEFAULT_DISPLAY);
}
@Test
@@ -112,13 +127,39 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
int expected = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(0)).onSystemUiStateChanged(expected);
+ verify(mCallback, times(0)).onSystemUiStateChanged(expected, DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void setFlag_receivedForDefaultDisplay() {
+ setFlags(FLAG_1);
+
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ }
+
+
+ @Test
+ public void init_registersWithDumpManager() {
+ mFlagsContainer.start();
+
+ verify(mDumpManager).registerNormalDumpable(any(), eq(mFlagsContainer));
+ }
+
+ @Test
+ public void destroy_unregistersWithDumpManager() {
+ mFlagsContainer.destroy();
+
+ verify(mDumpManager).unregisterDumpable(anyString());
}
private void setFlags(int... flags) {
- for (int i = 0; i < flags.length; i++) {
- mFlagsContainer.setFlag(flags[i], true);
+ setFlags(mFlagsContainer, flags);
+ }
+
+ private void setFlags(SysUiState instance, int... flags) {
+ for (int flag : flags) {
+ instance.setFlag(flag, true);
}
- mFlagsContainer.commitUpdate(DISPLAY_ID);
+ instance.commitUpdate();
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index e3fe24ca37c1..abecf33f611e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -46,12 +46,14 @@ import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsMode
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.util.animation.DisappearParameters
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.whenever
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -457,6 +459,20 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
}
}
+ @Test
+ fun isEditing() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ underTest.containerViewModel.editModeViewModel.startEditing()
+ runCurrent()
+ assertThat(underTest.isEditing).isTrue()
+
+ underTest.containerViewModel.editModeViewModel.stopEditing()
+ runCurrent()
+ assertThat(underTest.isEditing).isFalse()
+ }
+ }
+
private fun TestScope.setMediaState(state: MediaState) {
with(kosmos) {
val activeMedia = state == ACTIVE_MEDIA
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
index 3029928f070f..cb13b118fd68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
@@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.iUriGrantsManager
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.ui.viewmodel.iconProvider
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
@@ -32,6 +33,8 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.IconProvider
+import com.android.systemui.qs.panels.ui.viewmodel.toIconProvider
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
@@ -80,28 +83,32 @@ class TileRequestDialogViewModelTest : SysuiTestCase() {
@Test
fun uiState_beforeActivation_hasDefaultIcon_andCorrectData() =
kosmos.runTest {
- val expectedState =
- baseResultLegacyState.apply { icon = defaultIcon }.toUiState(mainResources)
+ val state = baseResultLegacyState.apply { icon = defaultIcon }
+
+ val expectedState = state.toUiState(mainResources)
+ val expectedIconProvider = state.toIconProvider()
with(underTest.uiState) {
expect.that(label).isEqualTo(TEST_LABEL)
expect.that(secondaryLabel).isEmpty()
- expect.that(state).isEqualTo(expectedState.state)
+ expect.that(this.state).isEqualTo(expectedState.state)
expect.that(handlesLongClick).isFalse()
expect.that(handlesSecondaryClick).isFalse()
- expect.that(icon).isEqualTo(defaultIcon)
expect.that(sideDrawable).isNull()
expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
}
+
+ expect.that(underTest.iconProvider).isEqualTo(expectedIconProvider)
}
@Test
fun uiState_afterActivation_hasCorrectIcon_andCorrectData() =
kosmos.runTest {
- val expectedState =
- baseResultLegacyState
- .apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) }
- .toUiState(mainResources)
+ val state =
+ baseResultLegacyState.apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) }
+
+ val expectedState = state.toUiState(mainResources)
+ val expectedIconProvider = state.toIconProvider()
underTest.activateIn(testScope)
runCurrent()
@@ -109,13 +116,13 @@ class TileRequestDialogViewModelTest : SysuiTestCase() {
with(underTest.uiState) {
expect.that(label).isEqualTo(TEST_LABEL)
expect.that(secondaryLabel).isEmpty()
- expect.that(state).isEqualTo(expectedState.state)
+ expect.that(this.state).isEqualTo(expectedState.state)
expect.that(handlesLongClick).isFalse()
expect.that(handlesSecondaryClick).isFalse()
- expect.that(icon).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable))
expect.that(sideDrawable).isNull()
expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
}
+ expect.that(underTest.iconProvider).isEqualTo(expectedIconProvider)
}
@Test
@@ -135,7 +142,7 @@ class TileRequestDialogViewModelTest : SysuiTestCase() {
underTest.activateIn(testScope)
runCurrent()
- assertThat(underTest.uiState.icon).isEqualTo(defaultIcon)
+ assertThat(underTest.iconProvider).isEqualTo(IconProvider.ConstantIcon(defaultIcon))
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
index 68a591dd075f..1d42424bc6ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
@@ -16,11 +16,9 @@
package com.android.systemui.qs.panels.ui.viewmodel
-
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FakeQSTile
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
@@ -48,45 +46,43 @@ class DetailsViewModelTest : SysuiTestCase() {
}
@Test
- fun changeTileDetailsViewModel() = with(kosmos) {
- testScope.runTest {
- val specs = listOf(
- spec,
- specNoDetails,
- )
- tileSpecRepository.setTiles(0, specs)
- runCurrent()
+ fun changeTileDetailsViewModel() =
+ with(kosmos) {
+ testScope.runTest {
+ val specs = listOf(spec, specNoDetails)
+ tileSpecRepository.setTiles(0, specs)
+ runCurrent()
- val tiles = currentTilesInteractor.currentTiles.value
+ val tiles = currentTilesInteractor.currentTiles.value
- assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
- assertThat(tiles[1].spec).isEqualTo(specNoDetails)
- (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false
+ assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
+ assertThat(tiles[1].spec).isEqualTo(specNoDetails)
+ (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false
- assertThat(underTest.activeTileDetails).isNull()
+ assertThat(underTest.activeTileDetails).isNull()
- // Click on the tile who has the `spec`.
- assertThat(underTest.onTileClicked(spec)).isTrue()
- assertThat(underTest.activeTileDetails).isNotNull()
- assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")
+ // Click on the tile who has the `spec`.
+ assertThat(underTest.onTileClicked(spec)).isTrue()
+ assertThat(underTest.activeTileDetails).isNotNull()
+ assertThat(underTest.activeTileDetails?.title).isEqualTo("internet")
- // Click on a tile who dose not have a valid spec.
- assertThat(underTest.onTileClicked(null)).isFalse()
- assertThat(underTest.activeTileDetails).isNull()
+ // Click on a tile who dose not have a valid spec.
+ assertThat(underTest.onTileClicked(null)).isFalse()
+ assertThat(underTest.activeTileDetails).isNull()
- // Click again on the tile who has the `spec`.
- assertThat(underTest.onTileClicked(spec)).isTrue()
- assertThat(underTest.activeTileDetails).isNotNull()
- assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")
+ // Click again on the tile who has the `spec`.
+ assertThat(underTest.onTileClicked(spec)).isTrue()
+ assertThat(underTest.activeTileDetails).isNotNull()
+ assertThat(underTest.activeTileDetails?.title).isEqualTo("internet")
- // Click on a tile who dose not have a detailed view.
- assertThat(underTest.onTileClicked(specNoDetails)).isFalse()
- assertThat(underTest.activeTileDetails).isNull()
+ // Click on a tile who dose not have a detailed view.
+ assertThat(underTest.onTileClicked(specNoDetails)).isFalse()
+ assertThat(underTest.activeTileDetails).isNull()
- underTest.closeDetailedView()
- assertThat(underTest.activeTileDetails).isNull()
+ underTest.closeDetailedView()
+ assertThat(underTest.activeTileDetails).isNull()
- assertThat(underTest.onTileClicked(null)).isFalse()
+ assertThat(underTest.onTileClicked(null)).isFalse()
+ }
}
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt
new file mode 100644
index 000000000000..7257a89c214b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.panels.ui.viewmodel
+
+import android.graphics.drawable.TestStubDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Supplier
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconProviderTest : SysuiTestCase() {
+
+ @Test
+ fun iconAndSupplier_prefersIcon() {
+ val state =
+ QSTile.State().apply {
+ icon = ResourceIcon.get(R.drawable.android)
+ iconSupplier = Supplier { QSTileImpl.DrawableIcon(TestStubDrawable()) }
+ }
+ val iconProvider = state.toIconProvider()
+
+ assertThat(iconProvider).isEqualTo(IconProvider.ConstantIcon(state.icon))
+ }
+
+ @Test
+ fun iconOnly_hasIcon() {
+ val state = QSTile.State().apply { icon = ResourceIcon.get(R.drawable.android) }
+ val iconProvider = state.toIconProvider()
+
+ assertThat(iconProvider).isEqualTo(IconProvider.ConstantIcon(state.icon))
+ }
+
+ @Test
+ fun supplierOnly_hasIcon() {
+ val state =
+ QSTile.State().apply {
+ iconSupplier = Supplier { ResourceIcon.get(R.drawable.android) }
+ }
+ val iconProvider = state.toIconProvider()
+
+ assertThat(iconProvider).isEqualTo(IconProvider.IconSupplier(state.iconSupplier))
+ }
+
+ @Test
+ fun noIconOrSupplier_iconNull() {
+ val state = QSTile.State()
+ val iconProvider = state.toIconProvider()
+
+ assertThat(iconProvider).isEqualTo(IconProvider.Empty)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt
index 9c8e3225f3a4..b144f0678471 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt
@@ -18,7 +18,6 @@ package com.android.systemui.qs.panels.ui.viewmodel
import android.content.res.Resources
import android.content.res.mainResources
-import android.graphics.drawable.TestStubDrawable
import android.service.quicksettings.Tile
import android.widget.Button
import android.widget.Switch
@@ -27,12 +26,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import java.util.function.Supplier
import org.junit.Test
import org.junit.runner.RunWith
@@ -267,45 +263,6 @@ class TileUiStateTest : SysuiTestCase() {
.contains(resources.getString(R.string.tile_unavailable))
}
- @Test
- fun iconAndSupplier_prefersIcon() {
- val state =
- QSTile.State().apply {
- icon = ResourceIcon.get(R.drawable.android)
- iconSupplier = Supplier { QSTileImpl.DrawableIcon(TestStubDrawable()) }
- }
- val uiState = state.toUiState()
-
- assertThat(uiState.icon).isEqualTo(state.icon)
- }
-
- @Test
- fun iconOnly_hasIcon() {
- val state = QSTile.State().apply { icon = ResourceIcon.get(R.drawable.android) }
- val uiState = state.toUiState()
-
- assertThat(uiState.icon).isEqualTo(state.icon)
- }
-
- @Test
- fun supplierOnly_hasIcon() {
- val state =
- QSTile.State().apply {
- iconSupplier = Supplier { ResourceIcon.get(R.drawable.android) }
- }
- val uiState = state.toUiState()
-
- assertThat(uiState.icon).isEqualTo(state.iconSupplier.get())
- }
-
- @Test
- fun noIconOrSupplier_iconNull() {
- val state = QSTile.State()
- val uiState = state.toUiState()
-
- assertThat(uiState.icon).isNull()
- }
-
private fun QSTile.State.toUiState() = toUiState(resources)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index c3089761effc..5bde7ad27b7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -691,11 +691,11 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
var currentModel: TileDetailsViewModel? = null
val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model }
tiles!![0].tile.getDetailsViewModel(setCurrentModel)
- assertThat(currentModel?.getTitle()).isEqualTo("a")
+ assertThat(currentModel?.title).isEqualTo("a")
currentModel = null
tiles!![1].tile.getDetailsViewModel(setCurrentModel)
- assertThat(currentModel?.getTitle()).isEqualTo("b")
+ assertThat(currentModel?.title).isEqualTo("b")
currentModel = null
tiles!![2].tile.getDetailsViewModel(setCurrentModel)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt
new file mode 100644
index 000000000000..1adba6fcd45d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.app.Flags
+import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.service.quicksettings.Tile
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.shared.QSSettingsPackageRepository
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
+
+@EnableFlags(Flags.FLAG_MODES_UI)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class ModesDndTileTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
+
+ @Mock private lateinit var qsHost: QSHost
+
+ @Mock private lateinit var metricsLogger: MetricsLogger
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Mock private lateinit var qsLogger: QSLogger
+
+ @Mock private lateinit var uiEventLogger: QsEventLogger
+
+ @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider
+
+ @Mock private lateinit var dialogDelegate: ModesDialogDelegate
+
+ @Mock private lateinit var settingsPackageRepository: QSSettingsPackageRepository
+
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val tileDataInteractor =
+ ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
+ private val mapper = ModesDndTileMapper(context.resources, context.theme)
+
+ private lateinit var userActionInteractor: ModesDndTileUserActionInteractor
+ private lateinit var secureSettings: SecureSettings
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var underTest: ModesDndTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ secureSettings = FakeSettings()
+
+ // Allow the tile to load resources
+ whenever(qsHost.context).thenReturn(context)
+ whenever(qsHost.userContext).thenReturn(context)
+
+ whenever(qsTileConfigProvider.getConfig(any()))
+ .thenReturn(
+ QSTileConfigTestBuilder.build {
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_dnd_icon_off,
+ labelRes = R.string.quick_settings_dnd_label,
+ )
+ }
+ )
+
+ userActionInteractor =
+ ModesDndTileUserActionInteractor(
+ kosmos.mainCoroutineContext,
+ inputHandler,
+ dialogDelegate,
+ kosmos.zenModeInteractor,
+ kosmos.modesDialogEventLogger,
+ settingsPackageRepository,
+ )
+
+ underTest =
+ ModesDndTile(
+ qsHost,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ qsTileConfigProvider,
+ tileDataInteractor,
+ mapper,
+ userActionInteractor,
+ )
+
+ underTest.initialize()
+ underTest.setListening(Object(), true)
+
+ testableLooper.processAllMessages()
+ }
+
+ @After
+ fun tearDown() {
+ underTest.destroy()
+ testableLooper.processAllMessages()
+ }
+
+ @Test
+ fun stateUpdatesOnChange() =
+ testScope.runTest {
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+
+ zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND)
+ runCurrent()
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ }
+
+ @Test
+ fun handleUpdateState_withModel_updatesState() =
+ testScope.runTest {
+ val tileState =
+ BooleanState().apply {
+ state = Tile.STATE_INACTIVE
+ secondaryLabel = "Old secondary label"
+ }
+ val model = ModesDndTileModel(isActivated = true)
+
+ underTest.handleUpdateState(tileState, model)
+
+ assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(tileState.secondaryLabel).isEqualTo("On")
+ }
+
+ @Test
+ fun handleUpdateState_withNull_updatesState() =
+ testScope.runTest {
+ val tileState =
+ BooleanState().apply {
+ state = Tile.STATE_INACTIVE
+ secondaryLabel = "Old secondary label"
+ }
+ zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND)
+ runCurrent()
+
+ underTest.handleUpdateState(tileState, null)
+
+ assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(tileState.secondaryLabel).isEqualTo("On")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt
index 00ee1c36590c..1b497a2b36ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt
@@ -24,6 +24,7 @@ import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager
import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.shared.QSSettingsPackageRepository
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
@@ -40,6 +41,7 @@ import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@EnabledOnRavenwood
@@ -53,14 +55,18 @@ class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() {
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var dialogManager: HearingDevicesDialogManager
+ @Mock private lateinit var settingsPackageRepository: QSSettingsPackageRepository
@Before
fun setUp() {
+ whenever(settingsPackageRepository.getSettingsPackageName())
+ .thenReturn(SETTINGS_PACKAGE_NAME)
underTest =
HearingDevicesTileUserActionInteractor(
testScope.coroutineContext,
inputHandler,
dialogManager,
+ settingsPackageRepository,
)
}
@@ -91,6 +97,11 @@ class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() {
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
assertThat(it.intent.action).isEqualTo(Settings.ACTION_HEARING_DEVICES_SETTINGS)
+ assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE_NAME)
}
}
+
+ companion object {
+ private const val SETTINGS_PACKAGE_NAME = "com.android.settings"
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt
new file mode 100644
index 000000000000..23d7b86df875
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import android.app.Flags
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnableFlags(Flags.FLAG_MODES_UI)
+@RunWith(AndroidJUnit4::class)
+class ModesDndTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val dispatcher = kosmos.testDispatcher
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+
+ private val underTest by lazy {
+ ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI_DND_TILE)
+ fun availability_flagOn_isTrue() =
+ testScope.runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+ assertThat(availability).containsExactly(true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI_DND_TILE)
+ fun availability_flagOff_isFalse() =
+ testScope.runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+ assertThat(availability).containsExactly(false)
+ }
+
+ @Test
+ fun tileData_dndChanges_updateActivated() =
+ testScope.runTest {
+ val model by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ assertThat(model!!.isActivated).isFalse()
+
+ zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND)
+ runCurrent()
+ assertThat(model!!.isActivated).isTrue()
+
+ zenModeRepository.deactivateMode(TestModeBuilder.MANUAL_DND)
+ runCurrent()
+ assertThat(model!!.isActivated).isFalse()
+ }
+
+ @Test
+ fun tileData_otherModeChanges_notActivated() =
+ testScope.runTest {
+ val model by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ assertThat(model!!.isActivated).isFalse()
+
+ zenModeRepository.addMode("Other mode")
+ runCurrent()
+ assertThat(model!!.isActivated).isFalse()
+
+ zenModeRepository.activateMode("Other mode")
+ runCurrent()
+ assertThat(model!!.isActivated).isFalse()
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..0a35b428bbc9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.shared.QSSettingsPackageRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(android.app.Flags.FLAG_MODES_UI)
+class ModesDndTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val inputHandler = kosmos.qsTileIntentUserInputHandler
+ private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val zenModeInteractor = kosmos.zenModeInteractor
+ private val settingsPackageRepository = mock<QSSettingsPackageRepository>()
+
+ private val underTest =
+ ModesDndTileUserActionInteractor(
+ kosmos.mainCoroutineContext,
+ inputHandler,
+ mockDialogDelegate,
+ zenModeInteractor,
+ kosmos.modesDialogEventLogger,
+ settingsPackageRepository,
+ )
+
+ @Before
+ fun setUp() {
+ whenever(settingsPackageRepository.getSettingsPackageName()).thenReturn(SETTINGS_PACKAGE)
+ }
+
+ @Test
+ fun handleClick_dndActive_deactivatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+ zenModeRepository.activateMode(MANUAL_DND)
+ assertThat(dndMode?.isActive).isTrue()
+
+ underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(true)))
+
+ assertThat(dndMode?.isActive).isFalse()
+ }
+
+ @Test
+ fun handleClick_dndInactive_activatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+ assertThat(dndMode?.isActive).isFalse()
+
+ underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(false)))
+
+ assertThat(dndMode?.isActive).isTrue()
+ }
+
+ @Test
+ fun handleLongClick_active_opensSettings() =
+ testScope.runTest {
+ zenModeRepository.activateMode(MANUAL_DND)
+ runCurrent()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(true)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE)
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+ .isEqualTo(MANUAL_DND.id)
+ }
+ }
+
+ @Test
+ fun handleLongClick_inactive_opensSettings() =
+ testScope.runTest {
+ zenModeRepository.activateMode(MANUAL_DND)
+ zenModeRepository.deactivateMode(MANUAL_DND)
+ runCurrent()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(false)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE)
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+ .isEqualTo(MANUAL_DND.id)
+ }
+ }
+
+ companion object {
+ private const val SETTINGS_PACKAGE = "the.settings.package"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt
new file mode 100644
index 000000000000..29f642a4325d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.ui
+
+import android.app.Flags
+import android.graphics.drawable.TestStubDrawable
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_MODES_UI)
+class ModesDndTileMapperTest : SysuiTestCase() {
+ val config =
+ QSTileConfigTestBuilder.build {
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_dnd_icon_off,
+ labelRes = R.string.quick_settings_modes_label,
+ )
+ }
+
+ val underTest =
+ ModesDndTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ )
+
+ @Test
+ fun map_inactiveState() {
+ val model = ModesDndTileModel(isActivated = false)
+
+ val state = underTest.map(config, model)
+
+ assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
+ assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_off)
+ assertThat(state.secondaryLabel).isEqualTo("Off")
+ }
+
+ @Test
+ fun map_activeState() {
+ val model = ModesDndTileModel(isActivated = true)
+
+ val state = underTest.map(config, model)
+
+ assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
+ assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_on)
+ assertThat(state.secondaryLabel).isEqualTo("On")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 9adf24f32c0c..1743e056b65c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -863,7 +863,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasUdfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -885,7 +885,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasUdfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -907,7 +907,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasSfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -930,7 +930,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasSfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1033,7 +1033,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasSfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1056,7 +1056,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasSfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1079,7 +1079,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasSfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1102,7 +1102,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
- collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+ collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)
setupBiometricAuth(hasSfps = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1160,7 +1160,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
- fun playsFaceErrorHaptics_nonSfps_coEx() =
+ fun skipsFaceErrorHaptics_nonSfps_coEx() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1172,15 +1172,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
underTest.start()
updateFaceAuthStatus(isSuccess = false)
- assertThat(playErrorHaptic).isNotNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper).vibrateAuthError(anyString())
+ assertThat(playErrorHaptic).isNull()
+ verify(vibratorHelper, never()).vibrateAuthError(anyString())
verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
}
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
- fun playsMSDLFaceErrorHaptics_nonSfps_coEx() =
+ fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1192,10 +1191,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
underTest.start()
updateFaceAuthStatus(isSuccess = false)
- assertThat(playErrorHaptic).isNotNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
- assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ assertThat(playErrorHaptic).isNull()
+ assertThat(msdlPlayer.latestTokenPlayed).isNull()
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt
new file mode 100644
index 000000000000..18ebd4d08486
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scene.ui.view
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.fakeFalsingCollector
+import com.android.systemui.keyevent.domain.interactor.mockSysUIKeyEventHandler
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.testKosmos
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WindowRootViewKeyEventHandlerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val underTest: WindowRootViewKeyEventHandler = kosmos.windowRootViewKeyEventHandler
+
+ @Test
+ fun dispatchKeyEvent_forwardsDispatchKeyEvent() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ underTest.dispatchKeyEvent(keyEvent)
+ verify(mockSysUIKeyEventHandler).dispatchKeyEvent(keyEvent)
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_forwardsDispatchKeyEventPreIme() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ underTest.dispatchKeyEventPreIme(keyEvent)
+ verify(mockSysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent)
+ }
+
+ @Test
+ fun interceptMediaKey_forwardsInterceptMediaKey() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+ underTest.interceptMediaKey(keyEvent)
+ verify(mockSysUIKeyEventHandler).interceptMediaKey(keyEvent)
+ }
+
+ @Test
+ fun collectKeyEvent_forwardsCollectKeyEvent() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
+ underTest.collectKeyEvent(keyEvent)
+ assertEquals(keyEvent, fakeFalsingCollector.lastKeyEvent)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 0448ad517c6d..0a82f72f8f21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -30,6 +30,9 @@ import com.android.systemui.shared.Flags
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlin.test.Test
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.kotlin.any
@@ -43,10 +46,19 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
private val actionExecutor = mock<ActionExecutor>()
private val uiEventLogger = mock<UiEventLogger>()
private val actionsCallback = mock<ScreenshotActionsController.ActionsCallback>()
- private val actionIntentCreator = ActionIntentCreator(context, context.packageManager)
+ private val actionIntentCreator =
+ ActionIntentCreator(
+ context,
+ context.packageManager,
+ testScope.backgroundScope,
+ mainDispatcher,
+ )
private val request = ScreenshotData.forTesting(userHandle = UserHandle.OWNER)
private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
@@ -198,6 +210,7 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
context,
uiEventLogger,
actionIntentCreator,
+ testScope,
UUID.randomUUID(),
request,
actionExecutor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index d852a9dc0bb9..94db429c2225 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -17,7 +17,9 @@
package com.android.systemui.shade;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.TYPE_INTERNAL;
+import static com.android.systemui.display.data.repository.FakeDisplayRepositoryKt.display;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -47,6 +49,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.UserManager;
import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -70,6 +73,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor;
import com.android.systemui.common.ui.view.TouchHandlingView;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
@@ -291,6 +295,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
@Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
+ @Mock protected SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
protected KeyguardClockInteractor mKeyguardClockInteractor;
@@ -435,6 +440,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
return null;
}).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
+ var displayMock = display(TYPE_INTERNAL, /* flags= */ 0, /* id= */Display.DEFAULT_DISPLAY,
+ /* state= */ null);
+ when(mView.getDisplay()).thenReturn(displayMock);
// Any edge transition
when(mKeyguardTransitionInteractor.transition(any()))
.thenReturn(emptyFlow());
@@ -565,6 +573,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mShadeRepository,
mSysUIUnfoldComponent,
mSysUiState,
+ mSysUIStateDisplaysInteractor,
mKeyguardUnlockAnimationController,
mKeyguardIndicationController,
mNotificationListContainer,
@@ -589,7 +598,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mKeyguardClockPositionAlgorithm,
mMSDLPlayer,
mBrightnessMirrorShowingRepository,
- new BlurConfig(0f, 0f));
+ new BlurConfig(0f, 0f),
+ () -> mKosmos.getFakeShadeDisplaysRepository());
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 354c23d48916..89263fc42739 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -16,10 +16,16 @@
package com.android.systemui.shade;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.systemui.display.data.repository.FakeDisplayRepositoryKt.display;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -28,6 +34,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Build;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.HapticFeedbackConstants;
@@ -38,6 +45,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Flags;
import com.android.systemui.flags.DisableSceneContainer;
import com.google.android.msdl.data.model.MSDLToken;
@@ -182,4 +190,25 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ public void updateSystemUiStateFlags_updatesSysuiStateInteractor() {
+ var DISPLAY_ID = 10;
+ mKosmos.getFakeShadeDisplaysRepository().setPendingDisplayId(DISPLAY_ID);
+
+ mNotificationPanelViewController.updateSystemUiStateFlags();
+
+ verify(mSysUIStateDisplaysInteractor).setFlagsExclusivelyToDisplay(eq(DISPLAY_ID), any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ public void updateSystemUiStateFlags_flagOff_doesNotUpdateSysuiStateInteractor() {
+ mNotificationPanelViewController.updateSystemUiStateFlags();
+
+ verify(mSysUIStateDisplaysInteractor, never()).setFlagsExclusivelyToDisplay(anyInt(),
+ any());
+ }
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 3407cd50e76f..3788049256a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -40,8 +40,10 @@ import android.app.IActivityManager;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.FlagsParameterization;
-import android.provider.Settings;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import android.view.WindowManager;
@@ -50,6 +52,7 @@ import androidx.test.filters.SmallTest;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -71,11 +74,11 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.util.settings.FakeSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -94,6 +97,9 @@ import java.util.concurrent.Executor;
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
+ @Rule public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock private ViewCaptureAwareWindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
@Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
@@ -113,7 +119,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
- private FakeSettings mSecureSettings;
private final Executor mMainExecutor = MoreExecutors.directExecutor();
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -135,9 +140,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mSecureSettings = new FakeSettings();
- mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 0);
-
// Preferred refresh rate is equal to the first displayMode's refresh rate
mPreferredRefreshRate = mContext.getDisplay().getSystemSupportedModes()[0].getRefreshRate();
overrideResource(
@@ -171,7 +173,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mKosmos.getNotificationShadeWindowModel(),
- mSecureSettings,
mKosmos::getCommunalInteractor,
mKosmos.getShadeLayoutParams());
mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
@@ -272,6 +273,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BLURRED_SHADE_VISIBLE)
public void setBackgroundBlurRadius_expandedWithBlurs() {
mNotificationShadeWindowController.setBackgroundBlurRadius(10);
verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE));
@@ -355,19 +357,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
- public void setKeyguardShowingWithSecureWindowsDisabled_disablesSecureFlag() {
- mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 1);
- mNotificationShadeWindowController.setBouncerShowing(true);
-
- verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
- assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
- assertThat(
- (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY)
- != 0)
- .isTrue();
- }
-
- @Test
public void setKeyguardNotShowing_disablesSecureFlag() {
mNotificationShadeWindowController.setBouncerShowing(false);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 59e3a2ec08e5..43983593f236 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.res.R
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractorPassThrough
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
@@ -69,7 +70,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
@@ -85,12 +85,12 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@@ -175,13 +175,14 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
+ val falsingCollector = FalsingCollectorFake()
controller =
NotificationShadeWindowViewController(
blurUtils,
windowRootViewModelFactory,
choreographer,
lockscreenShadeTransitionController,
- FalsingCollectorFake(),
+ falsingCollector,
statusBarStateController,
dockManager,
notificationShadeDepthController,
@@ -212,7 +213,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
featureFlags,
FakeSystemClock(),
- Mockito.mock(SysUIKeyEventHandler::class.java),
+ WindowRootViewKeyEventHandler({ mock<SysUIKeyEventHandler>() }, falsingCollector),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
index 9498daaf0b07..c635c7ff69a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -67,7 +67,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
fun policy_changing_propagatedFromTheLatestPolicy() =
testScope.runTest {
val underTest = createUnderTest()
- val displayIds by collectValues(underTest.displayId)
+ val displayIds by collectValues(underTest.pendingDisplayId)
assertThat(displayIds).containsExactly(0)
@@ -98,7 +98,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
DEVELOPMENT_SHADE_DISPLAY_AWARENESS,
FakeShadeDisplayPolicy.name,
)
- val displayId by collectLastValue(underTest.displayId)
+ val displayId by collectLastValue(underTest.pendingDisplayId)
displayRepository.addDisplay(displayId = 1)
@@ -161,7 +161,7 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
FakeShadeDisplayPolicy.name,
)
- val displayId by collectLastValue(underTest.displayId)
+ val displayId by collectLastValue(underTest.pendingDisplayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
FakeShadeDisplayPolicy.setDisplayId(2)
@@ -176,4 +176,17 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() {
assertThat(displayId).isEqualTo(2)
}
+
+ @Test
+ fun onDisplayChangedSucceeded_displayIdChanges() =
+ testScope.runTest {
+ val underTest = createUnderTest()
+ val displayId by collectLastValue(underTest.displayId)
+
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ underTest.onDisplayChangedSucceeded(1)
+
+ assertThat(displayId).isEqualTo(1)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
index fd6bc98b006c..f51d41bbc80b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
@@ -69,7 +69,7 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
@Test
fun commandShadeDisplayOverride_resetsDisplayId() =
testScope.runTest {
- val displayId by collectLastValue(shadeDisplaysRepository.displayId)
+ val displayId by collectLastValue(shadeDisplaysRepository.pendingDisplayId)
assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
val newDisplayId = 2
@@ -87,7 +87,7 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
@Test
fun commandShadeDisplayOverride_anyExternalDisplay_notOnDefaultAnymore() =
testScope.runTest {
- val displayId by collectLastValue(shadeDisplaysRepository.displayId)
+ val displayId by collectLastValue(shadeDisplaysRepository.pendingDisplayId)
assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
val newDisplayId = 2
displayRepository.addDisplay(displayId = newDisplayId)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
index e43c46b36a06..dd0ba00994ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.display
+import android.platform.test.annotations.EnableFlags
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
import android.view.MotionEvent
@@ -31,6 +32,7 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.data.repository.statusBarTouchShadeDisplayPolicy
import com.android.systemui.shade.domain.interactor.notificationElement
import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -41,6 +43,7 @@ import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index 0ad60b617194..24593f4455e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -18,16 +18,17 @@ package com.android.systemui.shade.domain.interactor
import android.content.res.Configuration
import android.content.res.mockResources
+import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.configurationRepository
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.scene.ui.view.mockShadeRootView
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.row.notificationRebindingTracker
@@ -48,6 +49,7 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
+@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
class ShadeDisplaysInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
@@ -82,7 +84,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInCorrectPosition_notAddedOrRemoved() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(0)
+ positionRepository.setPendingDisplayId(0)
underTest.start()
@@ -93,18 +95,19 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInWrongPosition_changes() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(1)
+ positionRepository.setPendingDisplayId(1)
underTest.start()
verify(shadeContext).reparentToDisplay(eq(1))
+ assertThat(positionRepository.displayId.value).isEqualTo(1)
}
@Test
fun start_shadeInWrongPosition_logsStartToLatencyTracker() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(1)
+ positionRepository.setPendingDisplayId(1)
underTest.start()
@@ -115,7 +118,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInWrongPosition_someNotificationsVisible_hiddenThenShown() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(1)
+ positionRepository.setPendingDisplayId(1)
activeNotificationRepository.setActiveNotifs(1)
underTest.start()
@@ -129,7 +132,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInWrongPosition_someNotificationsVisible_waitsForInflationsBeforeShowingNssl() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(1)
+ positionRepository.setPendingDisplayId(1)
activeNotificationRepository.setActiveNotifs(1)
val endRebinding = notificationRebindingTracker.trackRebinding("test")
@@ -155,7 +158,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInWrongPosition_noNotifications_nsslNotHidden() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(1)
+ positionRepository.setPendingDisplayId(1)
activeNotificationRepository.setActiveNotifs(0)
underTest.start()
@@ -170,7 +173,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInWrongPosition_waitsUntilMovedToDisplayReceived() =
testScope.runTest {
whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(1)
+ positionRepository.setPendingDisplayId(1)
activeNotificationRepository.setActiveNotifs(1)
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index a832f486ef32..04eb709b8894 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -94,6 +94,36 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
}
@Test
+ fun showClock_wideLayout_returnsTrue() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = true)
+
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
+ assertThat(underTest.showClock).isTrue()
+
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
+ assertThat(underTest.showClock).isTrue()
+ }
+
+ @Test
+ fun showClock_narrowLayoutOnNotificationsShade_returnsFalse() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = false)
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
+
+ assertThat(underTest.showClock).isFalse()
+ }
+
+ @Test
+ fun showClock_narrowLayoutOnQuickSettingsShade_returnsTrue() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = false)
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
+
+ assertThat(underTest.showClock).isTrue()
+ }
+
+ @Test
fun onShadeCarrierGroupClicked_launchesNetworkSettings() =
testScope.runTest {
val activityStarter = kosmos.activityStarter
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ddad230f04e9..2f2fafab53d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -25,8 +25,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
+import com.android.systemui.plugins.clocks.ClockAxisStyle
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
@@ -543,7 +543,7 @@ class ClockRegistryTest : SysuiTestCase() {
@Test
fun jsonDeserialization_fontAxes() {
- val expected = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f)))
+ val expected = ClockSettings(axes = ClockAxisStyle("KEY", 10f))
val json = JSONObject("""{"axes":[{"key":"KEY","value":10}]}""")
val actual = ClockSettings.fromJson(json)
assertEquals(expected, actual)
@@ -576,7 +576,7 @@ class ClockRegistryTest : SysuiTestCase() {
@Test
fun jsonSerialization_axisSettings() {
- val settings = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f)))
+ val settings = ClockSettings(axes = ClockAxisStyle("KEY", 10f))
val actual = ClockSettings.toJson(settings)
val expected = JSONObject("""{"metadata":{},"axes":[{"key":"KEY","value":10}]}""")
assertEquals(expected.toString(), actual.toString())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 4f301031e77c..0642467a001b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -19,6 +19,8 @@ package com.android.systemui.shared.clocks
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Drawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.FrameLayout
@@ -29,6 +31,7 @@ import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.ThemeConfig
+import com.android.systemui.shared.Flags
import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -103,14 +106,34 @@ class DefaultClockProviderTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_AMBIENT_AOD)
+ fun defaultClock_initialize_flagOff() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA)
+
+ clock.initialize(true, 0f, 0f, null)
+
+ // This is the default darkTheme color
+ val expectedColor = context.resources.getColor(android.R.color.system_accent1_100)
+ verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor)
+ verify(mockLargeClockView).setColors(DOZE_COLOR, expectedColor)
+ verify(mockSmallClockView).onTimeZoneChanged(notNull())
+ verify(mockLargeClockView).onTimeZoneChanged(notNull())
+ verify(mockSmallClockView).refreshTime()
+ verify(mockLargeClockView).refreshTime()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AMBIENT_AOD)
fun defaultClock_initialize() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA)
- clock.initialize(true, 0f, 0f, {})
+ clock.initialize(true, 0f, 0f, null)
- val expectedColor = 0
+ val expectedColor = Color.MAGENTA
verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor)
verify(mockLargeClockView).setColors(DOZE_COLOR, expectedColor)
verify(mockSmallClockView).onTimeZoneChanged(notNull())
@@ -165,8 +188,10 @@ class DefaultClockProviderTest : SysuiTestCase() {
}
@Test
- fun defaultClock_events_onThemeChanged_noSeed() {
- val expectedColor = 0
+ @DisableFlags(Flags.FLAG_AMBIENT_AOD)
+ fun defaultClock_events_onThemeChanged_noSeed_flagOff() {
+ // This is the default darkTheme color
+ val expectedColor = context.resources.getColor(android.R.color.system_accent1_100)
val clock = provider.createClock(DEFAULT_CLOCK_ID)
verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
@@ -180,6 +205,22 @@ class DefaultClockProviderTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_AMBIENT_AOD)
+ fun defaultClock_events_onThemeChanged_noSeedn() {
+ val expectedColor = Color.TRANSPARENT
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+
+ verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA)
+
+ clock.smallClock.events.onThemeChanged(ThemeConfig(true, null))
+ clock.largeClock.events.onThemeChanged(ThemeConfig(true, null))
+
+ verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA)
+ }
+
+ @Test
fun defaultClock_events_onThemeChanged_newSeed() {
val initSeedColor = 10
val newSeedColor = 20
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
index 4bd02e0fab22..17509dc6a80f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
@@ -19,6 +19,10 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ConditionExtensionsTest : SysuiTestCase() {
private lateinit var testScope: TestScope
+ private val testCallback =
+ Condition.Callback {
+ // This is a no-op
+ }
@Before
fun setUp() {
@@ -33,7 +37,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
assertThat(condition.isConditionSet).isFalse()
- condition.start()
+ condition.testStart()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isTrue()
}
@@ -46,7 +50,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
assertThat(condition.isConditionSet).isFalse()
- condition.start()
+ condition.testStart()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
}
@@ -56,7 +60,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
testScope.runTest {
val flow = emptyFlow<Boolean>()
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
- condition.start()
+ condition.testStop()
assertThat(condition.isConditionSet).isFalse()
assertThat(condition.isConditionMet).isFalse()
@@ -72,7 +76,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
strategy = Condition.START_EAGERLY,
initialValue = true,
)
- condition.start()
+ condition.testStart()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isTrue()
@@ -88,7 +92,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
strategy = Condition.START_EAGERLY,
initialValue = false,
)
- condition.start()
+ condition.testStart()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
@@ -99,7 +103,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
testScope.runTest {
val flow = MutableStateFlow(false)
val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
- condition.start()
+ condition.testStart()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
@@ -110,7 +114,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
flow.value = false
assertThat(condition.isConditionMet).isFalse()
- condition.stop()
+ condition.testStop()
}
@Test
@@ -120,10 +124,18 @@ class ConditionExtensionsTest : SysuiTestCase() {
val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
assertThat(flow.subscriptionCount.value).isEqualTo(0)
- condition.start()
+ condition.testStart()
assertThat(flow.subscriptionCount.value).isEqualTo(1)
- condition.stop()
+ condition.testStop()
assertThat(flow.subscriptionCount.value).isEqualTo(0)
}
+
+ fun Condition.testStart() {
+ addCallback(testCallback)
+ }
+
+ fun Condition.testStop() {
+ removeCallback(testCallback)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
index 5416536305fc..da660e2f6009 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
@@ -40,7 +40,7 @@ public class FakeCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 064fd485dab4..b80ff3466007 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogAssertKt;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginLifecycleManager;
import com.android.systemui.plugins.PluginListener;
@@ -138,11 +139,12 @@ public class PluginInstanceTest extends SysuiTestCase {
mVersionCheckResult = false;
assertFalse(mPluginInstance.hasError());
-
mPluginInstanceFactory.create(
mContext, mAppInfo, wrongVersionTestPluginComponentName,
TestPlugin.class, mPluginListener);
- mPluginInstance.onCreate();
+ LogAssertKt.assertRunnableLogsWtf(()-> {
+ mPluginInstance.onCreate();
+ });
assertTrue(mPluginInstance.hasError());
assertNull(mPluginInstance.getPlugin());
}
@@ -193,8 +195,10 @@ public class PluginInstanceTest extends SysuiTestCase {
mPluginInstance.onCreate();
assertFalse(mPluginInstance.hasError());
- Object result = mPluginInstance.getPlugin().methodThrowsError();
- assertNotNull(result); // Wrapper function should return non-null;
+ LogAssertKt.assertRunnableLogsWtf(()-> {
+ Object result = mPluginInstance.getPlugin().methodThrowsError();
+ assertNotNull(result); // Wrapper function should return non-null;
+ });
assertTrue(mPluginInstance.hasError());
assertNull(mPluginInstance.getPlugin());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt
new file mode 100644
index 000000000000..e04162bf990a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationGroupingUtilTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ private lateinit var underTest: NotificationGroupingUtil
+
+ private lateinit var testHelper: NotificationTestHelper
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(NotificationBundleUi.FLAG_NAME)
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ testHelper = NotificationTestHelper(mContext, mDependency)
+ }
+
+ @Test
+ fun showsTime() {
+ val row = testHelper.createRow()
+
+ underTest = NotificationGroupingUtil(row)
+ assertThat(underTest.showsTime(row)).isTrue()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index f54c28f4295b..998a7ea805a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -33,7 +33,7 @@ import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -82,7 +82,6 @@ import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.UserTracker;
@@ -108,7 +107,6 @@ import kotlinx.coroutines.flow.StateFlow;
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;
@@ -207,8 +205,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
private final Executor mMainExecutor = Runnable::run; // Direct executor
- @Rule public final LogWtfHandlerRule wtfHandlerRule = new LogWtfHandlerRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -697,7 +693,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mLockscreenUserManager.mConnectedToWifi.set(false);
// Sensitive Content notifications are always redacted
- assertEquals(REDACTION_TYPE_SENSITIVE_CONTENT,
+ assertEquals(REDACTION_TYPE_OTP,
mLockscreenUserManager.getRedactionType(mSensitiveContentNotif));
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index c9d910c530ea..01046cd10d87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -20,16 +20,29 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
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.Mockito.doNothing;
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.ActivityOptions;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
+import android.util.Pair;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -42,19 +55,37 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.util.kotlin.JavaAdapter;
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 java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class NotificationRemoteInputManagerTest extends SysuiTestCase {
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(NotificationBundleUi.FLAG_NAME);
+ }
+
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
@@ -69,14 +100,34 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
@Mock private NotificationClickNotifier mClickNotifier;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private PowerInteractor mPowerInteractor;
+ @Mock
+ NotificationRemoteInputManager.RemoteInputListener mRemoteInputListener;
+ private ActionClickLogger mActionClickLogger;
+ @Captor
+ ArgumentCaptor<NotificationRemoteInputManager.ClickHandler> mClickHandlerArgumentCaptor;
+ private Context mSpyContext;
+ private NotificationTestHelper mTestHelper;
private TestableNotificationRemoteInputManager mRemoteInputManager;
private NotificationEntry mEntry;
+ public NotificationRemoteInputManagerTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mSpyContext = spy(mContext);
+ doNothing().when(mSpyContext).startIntentSender(
+ any(), any(), anyInt(), anyInt(), anyInt(), any());
+
+
+ mTestHelper = new NotificationTestHelper(mSpyContext, mDependency);
+ mActionClickLogger = spy(new ActionClickLogger(logcatLogBuffer()));
+
mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext,
mock(NotifPipelineFlags.class),
mLockscreenUserManager,
@@ -87,9 +138,10 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
mRemoteInputUriController,
new RemoteInputControllerLogger(logcatLogBuffer()),
mClickNotifier,
- new ActionClickLogger(logcatLogBuffer()),
+ mActionClickLogger,
mock(JavaAdapter.class),
mock(ShadeInteractor.class));
+ mRemoteInputManager.setRemoteInputListener(mRemoteInputListener);
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -133,6 +185,70 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry));
}
+ @Test
+ public void testActionClick() throws Exception {
+ RemoteViews.RemoteResponse response = mock(RemoteViews.RemoteResponse.class);
+ when(response.getLaunchOptions(any())).thenReturn(
+ Pair.create(mock(Intent.class), mock(ActivityOptions.class)));
+ ExpandableNotificationRow row = getRowWithReplyAction();
+ View actionView = ((LinearLayout) row.getPrivateLayout().getExpandedChild().findViewById(
+ com.android.internal.R.id.actions)).getChildAt(0);
+ Notification n = getNotification(row);
+ CountDownLatch latch = new CountDownLatch(1);
+ Consumer<NotificationEntry> consumer = notificationEntry -> latch.countDown();
+ if (!NotificationBundleUi.isEnabled()) {
+ mRemoteInputManager.addActionPressListener(consumer);
+ }
+
+ mRemoteInputManager.getRemoteViewsOnClickHandler().onInteraction(
+ actionView,
+ n.actions[0].actionIntent,
+ response);
+
+ verify(mActionClickLogger).logInitialClick(row.getKey(), 0, n.actions[0].actionIntent);
+ verify(mClickNotifier).onNotificationActionClick(
+ eq(row.getKey()), eq(0), eq(n.actions[0]), any(), eq(false));
+ verify(mCallback).handleRemoteViewClick(eq(actionView), eq(n.actions[0].actionIntent),
+ eq(false), eq(0), mClickHandlerArgumentCaptor.capture());
+
+ mClickHandlerArgumentCaptor.getValue().handleClick();
+ verify(mActionClickLogger).logStartingIntentWithDefaultHandler(
+ row.getKey(), n.actions[0].actionIntent, 0);
+
+ verify(mRemoteInputListener).releaseNotificationIfKeptForRemoteInputHistory(row.getKey());
+ if (NotificationBundleUi.isEnabled()) {
+ verify(row.getEntryAdapter()).onNotificationActionClicked();
+ } else {
+ latch.await(10, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private Notification getNotification(ExpandableNotificationRow row) {
+ if (NotificationBundleUi.isEnabled()) {
+ return row.getEntryAdapter().getSbn().getNotification();
+ } else {
+ return row.getEntry().getSbn().getNotification();
+ }
+ }
+
+ private ExpandableNotificationRow getRowWithReplyAction() throws Exception {
+ PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"),
+ PendingIntent.FLAG_IMMUTABLE);
+ Notification n = new Notification.Builder(mSpyContext, "")
+ .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+ .addAction(new Notification.Action(com.android.systemui.res.R.drawable.ic_person,
+ "reply", pi))
+ .build();
+ ExpandableNotificationRow row = mTestHelper.createRow(n);
+ row.onNotificationUpdated();
+ row.getPrivateLayout().setExpandedChild(Notification.Builder.recoverBuilder(mSpyContext, n)
+ .createBigContentView().apply(
+ mSpyContext,
+ row.getPrivateLayout(),
+ mRemoteInputManager.getRemoteViewsOnClickHandler()));
+ return row;
+ }
+
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
TestableNotificationRemoteInputManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 326d8ffd3c7c..3ecf302204bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -32,14 +32,12 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.res.R
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
@@ -77,7 +75,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val applicationScope = kosmos.testScope.backgroundScope
- @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -87,6 +84,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var wallpaperController: WallpaperController
@Mock private lateinit var wallpaperInteractor: WallpaperInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
+ @Mock private lateinit var shadeModeInteractor: ShadeModeInteractor
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@Mock private lateinit var root: View
@@ -103,7 +102,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
private var statusBarState = StatusBarState.SHADE
private val maxBlur = 150
private lateinit var notificationShadeDepthController: NotificationShadeDepthController
- private val configurationController = FakeConfigurationController()
@Before
fun setup() {
@@ -133,13 +131,11 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
wallpaperInteractor,
notificationShadeWindowController,
dozeParameters,
- context,
- ResourcesSplitShadeStateController(),
+ shadeModeInteractor,
windowRootViewBlurInteractor,
appZoomOutOptional,
applicationScope,
- dumpManager,
- configurationController,
+ dumpManager
)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -492,15 +488,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
private fun enableSplitShade() {
- setSplitShadeEnabled(true)
+ `when` (shadeModeInteractor.isSplitShade).thenReturn(true)
}
private fun disableSplitShade() {
- setSplitShadeEnabled(false)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- configurationController.notifyConfigurationChanged()
+ `when` (shadeModeInteractor.isSplitShade).thenReturn(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index fda4ab005446..485b9febc284 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -19,43 +19,51 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel
import android.app.PendingIntent
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
-import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CallChipViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val chipBackgroundView = mock<ChipBackgroundContainer>()
@@ -71,7 +79,7 @@ class CallChipViewModelTest : SysuiTestCase() {
private val mockExpandable: Expandable =
mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
- private val underTest by lazy { kosmos.callChipViewModel }
+ private val Kosmos.underTest by Kosmos.Fixture { callChipViewModel }
@Test
fun chip_noCall_isHidden() =
@@ -88,9 +96,10 @@ class CallChipViewModelTest : SysuiTestCase() {
kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- addOngoingCallState(startTimeMs = 0)
+ addOngoingCallState(startTimeMs = 0, isAppVisible = false)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse()
}
@Test
@@ -98,9 +107,10 @@ class CallChipViewModelTest : SysuiTestCase() {
kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- addOngoingCallState(startTimeMs = -2)
+ addOngoingCallState(startTimeMs = -2, isAppVisible = false)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse()
}
@Test
@@ -108,9 +118,82 @@ class CallChipViewModelTest : SysuiTestCase() {
kosmos.runTest {
val latest by collectLastValue(underTest.chip)
- addOngoingCallState(startTimeMs = 345)
+ addOngoingCallState(startTimeMs = 345, isAppVisible = false)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+ fun chipLegacy_inCallWithVisibleApp_zeroStartTime_isHiddenAsInactive() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addOngoingCallState(startTimeMs = 0, isAppVisible = true)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+ @EnableChipsModernization
+ fun chipWithReturnAnimation_inCallWithVisibleApp_zeroStartTime_isHiddenAsIconOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addOngoingCallState(startTimeMs = 0, isAppVisible = true)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue()
+ }
+
+ @Test
+ @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+ fun chipLegacy_inCallWithVisibleApp_negativeStartTime_isHiddenAsInactive() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addOngoingCallState(startTimeMs = -2, isAppVisible = true)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+ @EnableChipsModernization
+ fun chipWithReturnAnimation_inCallWithVisibleApp_negativeStartTime_isHiddenAsIconOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addOngoingCallState(startTimeMs = -2, isAppVisible = true)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue()
+ }
+
+ @Test
+ @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+ fun chipLegacy_inCallWithVisibleApp_positiveStartTime_isHiddenAsInactive() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addOngoingCallState(startTimeMs = 345, isAppVisible = true)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+ @EnableChipsModernization
+ fun chipWithReturnAnimation_inCallWithVisibleApp_positiveStartTime_isHiddenAsTimer() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ addOngoingCallState(startTimeMs = 345, isAppVisible = true)
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue()
}
@Test
@@ -419,5 +502,18 @@ class CallChipViewModelTest : SysuiTestCase() {
private const val PROMOTED_BACKGROUND_COLOR = 65
private const val PROMOTED_PRIMARY_TEXT_COLOR = 98
+
+ @get:Parameters(name = "{0}")
+ @JvmStatic
+ val flags: List<FlagsParameterization>
+ get() = buildList {
+ addAll(
+ FlagsParameterization.allCombinationsOf(
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarChipsReturnAnimations.FLAG_NAME,
+ )
+ )
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index 5d1950670777..7f8f5f43e775 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -39,6 +40,7 @@ import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class SingleNotificationChipInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
val factory = kosmos.singleNotificationChipInteractorFactory
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index 7ed2bd38bcd2..0b9b297130a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -34,6 +34,8 @@ import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.addNotif
+import com.android.systemui.statusbar.notification.data.repository.removeNotif
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
@@ -409,6 +411,63 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun shownNotificationChips_lastAppVisibleTimeMaintainedAcrossNotifAddsAndRemoves() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.shownNotificationChips)
+
+ val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
+ val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
+
+ // Have notif1's app start as showing and then hide later so we get the chip
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+ fakeSystemClock.setCurrentTimeMillis(9_000)
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = notif1Info.key,
+ uid = notif1Info.uid,
+ statusBarChipIcon = notif1Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ )
+ )
+ activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = false)
+
+ assertThat(latest!![0].key).isEqualTo(notif1Info.key)
+ assertThat(latest!![0].lastAppVisibleTime).isEqualTo(9_000)
+
+ // WHEN a new notification is added
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+ fakeSystemClock.setCurrentTimeMillis(10_000)
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = notif2Info.key,
+ uid = notif2Info.uid,
+ statusBarChipIcon = notif2Info.icon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ )
+ )
+ activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
+
+ // THEN the new notification is first
+ assertThat(latest!![0].key).isEqualTo(notif2Info.key)
+ assertThat(latest!![0].lastAppVisibleTime).isEqualTo(10_000)
+
+ // And THEN the original notification maintains its lastAppVisibleTime
+ assertThat(latest!![1].key).isEqualTo(notif1Info.key)
+ assertThat(latest!![1].lastAppVisibleTime).isEqualTo(9_000)
+
+ // WHEN notif1 is removed
+ fakeSystemClock.setCurrentTimeMillis(11_000)
+ activeNotificationListRepository.removeNotif(notif1Info.key)
+
+ // THEN notif2 still has its lastAppVisibleTime
+ assertThat(latest!![0].key).isEqualTo(notif2Info.key)
+ assertThat(latest!![0].lastAppVisibleTime).isEqualTo(10_000)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
fun shownNotificationChips_sortedByLastAppVisibleTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.shownNotificationChips)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 4993b5661373..b5cfc7e9080d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -573,6 +573,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(whenElapsed)
+ assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture)
+ .isFalse()
}
@Test
@@ -608,6 +610,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).startTimeMs)
.isEqualTo(whenElapsed)
+ assertThat((latest!![0] as OngoingActivityChipModel.Active.Timer).isEventInFuture)
+ .isTrue()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt
index 4e92540396d3..cd9970cfa614 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt
@@ -35,55 +35,153 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ChronometerStateTest : SysuiTestCase() {
- private lateinit var mockTimeSource: MutableTimeSource
+ private lateinit var fakeTimeSource: MutableTimeSource
@Before
fun setup() {
- mockTimeSource = MutableTimeSource()
+ fakeTimeSource = MutableTimeSource()
}
@Test
- fun initialText_isCorrect() = runTest {
- val state = ChronometerState(mockTimeSource, 0L)
- assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(0))
+ fun initialText_isEventInFutureFalse_timeIsNow() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = false)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0))
}
@Test
- fun textUpdates_withTime() = runTest {
- val startTime = 1000L
- val state = ChronometerState(mockTimeSource, startTime)
+ fun initialText_isEventInFutureFalse_timeInPast() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = false)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2))
+ }
+
+ @Test
+ fun initialText_isEventInFutureFalse_timeInFuture() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = false)
+ // When isEventInFuture=false, eventTimeMillis needs to be in the past if we want text to
+ // show
+ assertThat(state.currentTimeText).isNull()
+ }
+
+ @Test
+ fun initialText_isEventInFutureTrue_timeIsNow() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 3_000, isEventInFuture = true)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 0))
+ }
+
+ @Test
+ fun initialText_isEventInFutureTrue_timeInFuture() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 5_000, isEventInFuture = true)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 2))
+ }
+
+ @Test
+ fun initialText_isEventInFutureTrue_timeInPast() = runTest {
+ fakeTimeSource.time = 3_000
+ val state =
+ ChronometerState(fakeTimeSource, eventTimeMillis = 1_000, isEventInFuture = true)
+ // When isEventInFuture=true, eventTimeMillis needs to be in the future if we want text to
+ // show
+ assertThat(state.currentTimeText).isNull()
+ }
+
+ @Test
+ fun textUpdates_isEventInFutureFalse_timeInPast() = runTest {
+ val eventTime = 1000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
val job = launch { state.run() }
val elapsedTime = 5000L
- mockTimeSource.time = startTime + elapsedTime
+ fakeTimeSource.time = eventTime + elapsedTime
advanceTimeBy(elapsedTime)
assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000))
+ val additionalTime = 6000L
+ fakeTimeSource.time += additionalTime
+ advanceTimeBy(additionalTime)
+ assertThat(state.currentTimeText)
+ .isEqualTo(formatElapsedTime((elapsedTime + additionalTime) / 1000))
+
job.cancelAndJoin()
}
@Test
- fun textUpdates_toLargerValue() = runTest {
- val startTime = 1000L
- val state = ChronometerState(mockTimeSource, startTime)
+ fun textUpdates_isEventInFutureFalse_timeChangesFromFutureToPast() = runTest {
+ val eventTime = 15_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
val job = launch { state.run() }
- val elapsedTime = 15000L
- mockTimeSource.time = startTime + elapsedTime
- advanceTimeBy(elapsedTime)
- assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(elapsedTime / 1000))
+ // WHEN the time is 5 but the eventTime is 15
+ fakeTimeSource.time = 5_000L
+ advanceTimeBy(5_000L)
+ // THEN no text is shown
+ assertThat(state.currentTimeText).isNull()
+
+ // WHEN the time advances to 40
+ fakeTimeSource.time = 40_000L
+ advanceTimeBy(35_000)
+ // THEN text is shown as 25 seconds (40 - 15)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25))
job.cancelAndJoin()
}
@Test
- fun textUpdates_afterResettingBase() = runTest {
+ fun textUpdates_isEventInFutureTrue_timeInFuture() = runTest {
+ val eventTime = 15_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = 5_000L
+ advanceTimeBy(5_000L)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ val additionalTime = 6000L
+ fakeTimeSource.time += additionalTime
+ advanceTimeBy(additionalTime)
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 4))
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun textUpdates_isEventInFutureTrue_timeChangesFromFutureToPast() = runTest {
+ val eventTime = 15_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ // WHEN the time is 5 and the eventTime is 15
+ fakeTimeSource.time = 5_000L
+ advanceTimeBy(5_000L)
+ // THEN 10 seconds is shown
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ // WHEN the time advances to 40 (past the event time)
+ fakeTimeSource.time = 40_000L
+ advanceTimeBy(35_000)
+ // THEN no text is shown
+ assertThat(state.currentTimeText).isNull()
+
+ job.cancelAndJoin()
+ }
+
+ @Test
+ fun textUpdates_afterResettingBase_isEventInFutureFalse() = runTest {
val initialElapsedTime = 30000L
val startTime = 50000L
- val state = ChronometerState(mockTimeSource, startTime)
+ val state = ChronometerState(fakeTimeSource, startTime, isEventInFuture = false)
val job = launch { state.run() }
- mockTimeSource.time = startTime + initialElapsedTime
+ fakeTimeSource.time = startTime + initialElapsedTime
advanceTimeBy(initialElapsedTime)
assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(initialElapsedTime / 1000))
@@ -91,15 +189,68 @@ class ChronometerStateTest : SysuiTestCase() {
val newElapsedTime = 5000L
val newStartTime = 100000L
- val newState = ChronometerState(mockTimeSource, newStartTime)
+ val newState = ChronometerState(fakeTimeSource, newStartTime, isEventInFuture = false)
val newJob = launch { newState.run() }
- mockTimeSource.time = newStartTime + newElapsedTime
+ fakeTimeSource.time = newStartTime + newElapsedTime
advanceTimeBy(newElapsedTime)
assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(newElapsedTime / 1000))
newJob.cancelAndJoin()
}
+
+ @Test
+ fun textUpdates_afterResettingBase_isEventInFutureTrue() = runTest {
+ val initialElapsedTime = 40_000L
+ val eventTime = 50_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = initialElapsedTime
+ advanceTimeBy(initialElapsedTime)
+ // Time should be 50 - 40 = 10
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ job.cancelAndJoin()
+
+ val newElapsedTime = 75_000L
+ val newEventTime = 100_000L
+ val newState = ChronometerState(fakeTimeSource, newEventTime, isEventInFuture = true)
+ val newJob = launch { newState.run() }
+
+ fakeTimeSource.time = newElapsedTime
+ advanceTimeBy(newElapsedTime - initialElapsedTime)
+ // Time should be 100 - 75 = 25
+ assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 25))
+
+ newJob.cancelAndJoin()
+ }
+
+ @Test
+ fun textUpdates_afterResettingisEventInFuture() = runTest {
+ val initialElapsedTime = 40_000L
+ val eventTime = 50_000L
+ val state = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = true)
+ val job = launch { state.run() }
+
+ fakeTimeSource.time = initialElapsedTime
+ advanceTimeBy(initialElapsedTime)
+ // Time should be 50 - 40 = 10
+ assertThat(state.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 10))
+
+ job.cancelAndJoin()
+
+ val newElapsedTime = 70_000L
+ val newState = ChronometerState(fakeTimeSource, eventTime, isEventInFuture = false)
+ val newJob = launch { newState.run() }
+
+ fakeTimeSource.time = newElapsedTime
+ advanceTimeBy(newElapsedTime - initialElapsedTime)
+ // Time should be 70 - 50 = 20
+ assertThat(newState.currentTimeText).isEqualTo(formatElapsedTime(/* elapsedSeconds= */ 20))
+
+ newJob.cancelAndJoin()
+ }
}
/** A fake implementation of [TimeSource] that allows the caller to set the current time */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 1b5b0d6ff897..e39fa7099953 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -16,18 +16,19 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
+import android.content.Context
import android.content.DialogInterface
import android.content.packageManager
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
@@ -136,7 +137,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- addOngoingCallState()
+ addOngoingCallState(isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
@@ -162,7 +163,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
screenRecordState.value = ScreenRecordModel.DoingNothing
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- addOngoingCallState()
+ addOngoingCallState(isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
@@ -177,11 +178,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
// MediaProjection covers both share-to-app and cast-to-other-device
mediaProjectionState.value = MediaProjectionState.NotProjecting
- addOngoingCallState(key = notificationKey)
+ addOngoingCallState(key = notificationKey, isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
- assertIsCallChip(latest, notificationKey)
+ assertIsCallChip(latest, notificationKey, context)
}
@Test
@@ -189,14 +190,14 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
kosmos.runTest {
// Start with just the lowest priority chip shown
val callNotificationKey = "call"
- addOngoingCallState(key = callNotificationKey)
+ addOngoingCallState(key = callNotificationKey, isAppVisible = false)
// And everything else hidden
mediaProjectionState.value = MediaProjectionState.NotProjecting
screenRecordState.value = ScreenRecordModel.DoingNothing
val latest by collectLastValue(underTest.primaryChip)
- assertIsCallChip(latest, callNotificationKey)
+ assertIsCallChip(latest, callNotificationKey, context)
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -224,7 +225,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
val callNotificationKey = "call"
- addOngoingCallState(key = callNotificationKey)
+ addOngoingCallState(key = callNotificationKey, isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
@@ -241,7 +242,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN the lower priority call is used
- assertIsCallChip(latest, callNotificationKey)
+ assertIsCallChip(latest, callNotificationKey, context)
}
/** Regression test for b/347726238. */
@@ -395,18 +396,29 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
}
- fun assertIsCallChip(latest: OngoingActivityChipModel?, notificationKey: String) {
+ fun assertIsCallChip(
+ latest: OngoingActivityChipModel?,
+ notificationKey: String,
+ context: Context,
+ ) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
assertThat((latest as OngoingActivityChipModel.Active).key).isEqualTo(notificationKey)
if (StatusBarConnectedDisplays.isEnabled) {
assertNotificationIcon(latest, notificationKey)
- return
+ } else {
+ val contentDescription =
+ if (latest.icon is OngoingActivityChipModel.ChipIcon.SingleColorIcon) {
+ ((latest.icon) as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
+ .impl
+ .contentDescription
+ } else {
+ (latest.icon as OngoingActivityChipModel.ChipIcon.StatusBarView)
+ .contentDescription
+ }
+ assertThat(contentDescription.loadContentDescription(context))
+ .contains(context.getString(R.string.ongoing_call_content_description))
}
- val icon =
- ((latest.icon) as OngoingActivityChipModel.ChipIcon.SingleColorIcon).impl
- as Icon.Resource
- assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
}
private fun assertNotificationIcon(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 2887de38fe23..7135cf01394a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
+import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -61,6 +62,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.addNotifs
+import com.android.systemui.statusbar.notification.data.repository.removeNotif
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -172,6 +174,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
+ fun visibleChipKeys_allInactive() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ screenRecordState.value = ScreenRecordModel.DoingNothing
+ mediaProjectionState.value = MediaProjectionState.NotProjecting
+ setNotifs(emptyList())
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
fun primaryChip_screenRecordShow_restHidden_screenRecordShown() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
@@ -222,7 +236,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
fun primaryChip_screenRecordShowAndCallShow_screenRecordShown() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- addOngoingCallState("call")
+ addOngoingCallState("call", isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
@@ -235,16 +249,30 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
kosmos.runTest {
val callNotificationKey = "call"
screenRecordState.value = ScreenRecordModel.Recording
- addOngoingCallState(callNotificationKey)
+ addOngoingCallState(callNotificationKey, isAppVisible = false)
val latest by collectLastValue(underTest.chipsLegacy)
val unused by collectLastValue(underTest.chips)
assertIsScreenRecordChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary, callNotificationKey)
+ assertIsCallChip(latest!!.secondary, callNotificationKey, context)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ fun visibleChipKeys_screenRecordShowAndCallShow_hasBothKeys() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(callNotificationKey)
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
@EnableChipsModernization
@Test
fun chips_screenRecordAndCallActive_inThatOrder() =
@@ -258,7 +286,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertThat(latest!!.active.size).isEqualTo(2)
assertIsScreenRecordChip(latest!!.active[0])
- assertIsCallChip(latest!!.active[1], callNotificationKey)
+ assertIsCallChip(latest!!.active[1], callNotificationKey, context)
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(2)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
@@ -268,7 +296,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@Test
fun chipsLegacy_oneChip_notSquished() =
kosmos.runTest {
- addOngoingCallState()
+ addOngoingCallState(isAppVisible = false)
val latest by collectLastValue(underTest.chipsLegacy)
@@ -295,7 +323,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
val latest by collectLastValue(underTest.chipsLegacy)
@@ -322,12 +350,42 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableChipsModernization
+ @Test
+ fun chips_threeChips_isSmallPortrait_allSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.shortCriticalText = "Some text here"
+ }
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+
+ val latest by collectLastValue(underTest.chips)
+
+ // Squished chips are icon only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat(latest!!.active[2])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableChipsModernization
@Test
fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000)
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
val latest by collectLastValue(underTest.chipsLegacy)
@@ -373,7 +431,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
// WHEN there's 2 chips
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
// THEN they both become squished
assertThat(latest!!.primary)
@@ -429,7 +487,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
fun chipsLegacy_twoChips_isLandscape_notSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
// WHEN we're in landscape
val config =
@@ -475,7 +533,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
fun chipsLegacy_twoChips_isLargeScreen_notSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
// WHEN we're on a large screen
kosmos.displayStateRepository.setIsLargeScreen(true)
@@ -569,7 +627,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
screenRecordState.value = ScreenRecordModel.DoingNothing
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
@@ -584,13 +642,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
screenRecordState.value = ScreenRecordModel.DoingNothing
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- addOngoingCallState(key = "call")
+ addOngoingCallState(key = "call", isAppVisible = false)
val latest by collectLastValue(underTest.chipsLegacy)
val unused by collectLastValue(underTest.chips)
assertIsShareToAppChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary, callNotificationKey)
+ assertIsCallChip(latest!!.secondary, callNotificationKey, context)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
@@ -609,7 +667,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertThat(latest!!.active.size).isEqualTo(2)
assertIsShareToAppChip(latest!!.active[0])
- assertIsCallChip(latest!!.active[1], callNotificationKey)
+ assertIsCallChip(latest!!.active[1], callNotificationKey, context)
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(2)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
@@ -623,11 +681,11 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
mediaProjectionState.value = MediaProjectionState.NotProjecting
val callNotificationKey = "call"
- addOngoingCallState(key = callNotificationKey)
+ addOngoingCallState(key = callNotificationKey, isAppVisible = false)
val latest by collectLastValue(underTest.primaryChip)
- assertIsCallChip(latest, callNotificationKey)
+ assertIsCallChip(latest, callNotificationKey, context)
}
@DisableChipsModernization
@@ -639,12 +697,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// MediaProjection covers both share-to-app and cast-to-other-device
mediaProjectionState.value = MediaProjectionState.NotProjecting
- addOngoingCallState(key = callNotificationKey)
+ addOngoingCallState(key = callNotificationKey, isAppVisible = false)
val latest by collectLastValue(underTest.chipsLegacy)
val unused by collectLastValue(underTest.chips)
- assertIsCallChip(latest!!.primary, callNotificationKey)
+ assertIsCallChip(latest!!.primary, callNotificationKey, context)
assertThat(latest!!.secondary)
.isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
@@ -664,7 +722,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val unused by collectLastValue(underTest.chipsLegacy)
assertThat(latest!!.active.size).isEqualTo(1)
- assertIsCallChip(latest!!.active[0], callNotificationKey)
+ assertIsCallChip(latest!!.active[0], callNotificationKey, context)
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(3)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
@@ -824,7 +882,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@EnableChipsModernization
@Test
- fun chips_threePromotedNotifs_topTwoActiveThirdInOverflow() =
+ fun chips_fourPromotedNotifs_topThreeActiveFourthInOverflow() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val unused by collectLastValue(underTest.chipsLegacy)
@@ -832,6 +890,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val firstIcon = createStatusBarIconViewOrNull()
val secondIcon = createStatusBarIconViewOrNull()
val thirdIcon = createStatusBarIconViewOrNull()
+ val fourthIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -852,18 +911,95 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
promotedContent =
PromotedNotificationContentModel.Builder("thirdNotif").build(),
),
+ activeNotificationModel(
+ key = "fourthNotif",
+ statusBarChipIcon = fourthIcon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("fourthNotif").build(),
+ ),
)
)
- assertThat(latest!!.active.size).isEqualTo(2)
+ assertThat(latest!!.active.size).isEqualTo(3)
assertIsNotifChip(latest!!.active[0], context, firstIcon, "firstNotif")
assertIsNotifChip(latest!!.active[1], context, secondIcon, "secondNotif")
+ assertIsNotifChip(latest!!.active[2], context, thirdIcon, "thirdNotif")
assertThat(latest!!.overflow.size).isEqualTo(1)
- assertIsNotifChip(latest!!.overflow[0], context, thirdIcon, "thirdNotif")
+ assertIsNotifChip(latest!!.overflow[0], context, fourthIcon, "fourthNotif")
assertThat(latest!!.inactive.size).isEqualTo(4)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
}
+ @Test
+ @DisableChipsModernization
+ fun visibleChipKeys_chipsModOff_threePromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ ),
+ )
+ )
+
+ assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun visibleChipKeys_chipsModOn_fourPromotedNotifs_topThreeInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "fourthNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("fourthNotif").build(),
+ ),
+ )
+ )
+
+ assertThat(latest).containsExactly("firstNotif", "secondNotif", "thirdNotif").inOrder()
+ }
+
@DisableChipsModernization
@Test
fun chipsLegacy_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
@@ -872,7 +1008,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val unused by collectLastValue(underTest.chips)
val callNotificationKey = "call"
- addOngoingCallState(callNotificationKey)
+ addOngoingCallState(callNotificationKey, isAppVisible = false)
val firstIcon = createStatusBarIconViewOrNull()
activeNotificationListRepository.addNotifs(
@@ -892,14 +1028,14 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsCallChip(latest!!.primary, callNotificationKey)
+ assertIsCallChip(latest!!.primary, callNotificationKey, context)
assertIsNotifChip(latest!!.secondary, context, firstIcon, "firstNotif")
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
@EnableChipsModernization
@Test
- fun chips_callAndPromotedNotifs_callAndFirstNotifActiveSecondNotifInOverflow() =
+ fun chips_callAndPromotedNotifs_callAndFirstTwoNotifsActive_thirdNotifInOverflow() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val unused by collectLastValue(underTest.chipsLegacy)
@@ -907,6 +1043,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val callNotificationKey = "call"
val firstIcon = createStatusBarIconViewOrNull()
val secondIcon = createStatusBarIconViewOrNull()
+ val thirdIcon = createStatusBarIconViewOrNull()
addOngoingCallState(key = callNotificationKey)
activeNotificationListRepository.addNotifs(
listOf(
@@ -922,27 +1059,34 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
promotedContent =
PromotedNotificationContentModel.Builder("secondNotif").build(),
),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = thirdIcon,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ ),
)
)
- assertThat(latest!!.active.size).isEqualTo(2)
- assertIsCallChip(latest!!.active[0], callNotificationKey)
+ assertThat(latest!!.active.size).isEqualTo(3)
+ assertIsCallChip(latest!!.active[0], callNotificationKey, context)
assertIsNotifChip(latest!!.active[1], context, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.active[2], context, secondIcon, "secondNotif")
assertThat(latest!!.overflow.size).isEqualTo(1)
- assertIsNotifChip(latest!!.overflow[0], context, secondIcon, "secondNotif")
+ assertIsNotifChip(latest!!.overflow[0], context, thirdIcon, "thirdNotif")
assertThat(latest!!.inactive.size).isEqualTo(3)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
}
@DisableChipsModernization
@Test
- fun chipsLegacy_screenRecordAndCallAndPromotedNotifs_notifsNotShown() =
+ fun chipsLegacy_screenRecordAndCallAndPromotedNotif_notifNotShown() =
kosmos.runTest {
val callNotificationKey = "call"
val latest by collectLastValue(underTest.chipsLegacy)
val unused by collectLastValue(underTest.chips)
- addOngoingCallState(callNotificationKey)
+ addOngoingCallState(callNotificationKey, isAppVisible = false)
screenRecordState.value = ScreenRecordModel.Recording
activeNotificationListRepository.addNotif(
activeNotificationModel(
@@ -953,13 +1097,71 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
assertIsScreenRecordChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary, callNotificationKey)
+ assertIsCallChip(latest!!.secondary, callNotificationKey, context)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ @DisableChipsModernization
+ fun visibleChipKeys_chipsModOff_screenRecordAndCallAndPromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ addOngoingCallState(callNotificationKey)
+ screenRecordState.value = ScreenRecordModel.Recording
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ )
+ )
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ )
+ )
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun visibleChipKeys_chipsModOn_screenRecordAndCallAndPromotedNotifs_topThreeInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ addOngoingCallState(callNotificationKey)
+ screenRecordState.value = ScreenRecordModel.Recording
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ )
+ )
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ )
+ )
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey, "notif1")
+ .inOrder()
+ }
+
@EnableChipsModernization
@Test
- fun chips_screenRecordAndCallAndPromotedNotif_notifInOverflow() =
+ fun chips_screenRecordAndCallAndPromotedNotifs_secondNotifInOverflow() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val unused by collectLastValue(underTest.chipsLegacy)
@@ -976,11 +1178,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
addOngoingCallState(key = callNotificationKey)
- assertThat(latest!!.active.size).isEqualTo(2)
+ // This is the overflow notif
+ val notifIcon2 = createStatusBarIconViewOrNull()
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = notifIcon2,
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ )
+ )
+
+ assertThat(latest!!.active.size).isEqualTo(3)
assertIsScreenRecordChip(latest!!.active[0])
- assertIsCallChip(latest!!.active[1], callNotificationKey)
+ assertIsCallChip(latest!!.active[1], callNotificationKey, context)
+ assertIsNotifChip(latest!!.active[2], context, notifIcon, "notif")
assertThat(latest!!.overflow.size).isEqualTo(1)
- assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif")
+ assertIsNotifChip(latest!!.overflow[0], context, notifIcon2, "notif2")
assertThat(latest!!.inactive.size).isEqualTo(2)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
}
@@ -1010,10 +1223,10 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertIsNotifChip(latest, context, notifIcon, "notif")
// WHEN the higher priority call chip is added
- addOngoingCallState(callNotificationKey)
+ addOngoingCallState(callNotificationKey, isAppVisible = false)
// THEN the higher priority call chip is used
- assertIsCallChip(latest, callNotificationKey)
+ assertIsCallChip(latest, callNotificationKey, context)
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -1041,7 +1254,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- addOngoingCallState(callNotificationKey)
+ addOngoingCallState(callNotificationKey, isAppVisible = false)
val notifIcon = createStatusBarIconViewOrNull()
activeNotificationListRepository.addNotif(
activeNotificationModel(
@@ -1066,7 +1279,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN the lower priority call is used
- assertIsCallChip(latest, callNotificationKey)
+ assertIsCallChip(latest, callNotificationKey, context)
// WHEN the higher priority call is removed
removeOngoingCallState(key = callNotificationKey)
@@ -1103,11 +1316,11 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
// WHEN the higher priority call chip is added
- addOngoingCallState(callNotificationKey)
+ addOngoingCallState(callNotificationKey, isAppVisible = false)
// THEN the higher priority call chip is used as primary and notif is demoted to
// secondary
- assertIsCallChip(latest!!.primary, callNotificationKey)
+ assertIsCallChip(latest!!.primary, callNotificationKey, context)
assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
@@ -1122,7 +1335,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// THEN the higher priority media projection chip is used as primary and call is demoted
// to secondary (and notif is dropped altogether)
assertIsShareToAppChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary, callNotificationKey)
+ assertIsCallChip(latest!!.secondary, callNotificationKey, context)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
// WHEN the higher priority screen record chip is added
@@ -1155,15 +1368,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@Test
fun chips_movesChipsAroundAccordingToPriority() =
kosmos.runTest {
+ systemClock.setCurrentTimeMillis(10_000)
val callNotificationKey = "call"
// Start with just the lowest priority chip active
- val notifIcon = createStatusBarIconViewOrNull()
+ val notif1Icon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
- key = "notif",
- statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ key = "notif1",
+ statusBarChipIcon = notif1Icon,
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
)
)
)
@@ -1175,7 +1389,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val unused by collectLastValue(underTest.chipsLegacy)
assertThat(latest!!.active.size).isEqualTo(1)
- assertIsNotifChip(latest!!.active[0], context, notifIcon, "notif")
+ assertIsNotifChip(latest!!.active[0], context, notif1Icon, "notif1")
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(4)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
@@ -1183,10 +1397,10 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// WHEN the higher priority call chip is added
addOngoingCallState(key = callNotificationKey)
- // THEN the higher priority call chip and notif are active in that order
+ // THEN the higher priority call chip and notif1 are active in that order
assertThat(latest!!.active.size).isEqualTo(2)
- assertIsCallChip(latest!!.active[0], callNotificationKey)
- assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif")
+ assertIsCallChip(latest!!.active[0], callNotificationKey, context)
+ assertIsNotifChip(latest!!.active[1], context, notif1Icon, "notif1")
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(3)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
@@ -1199,56 +1413,63 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
createTask(taskId = 1),
)
- // THEN the higher priority media projection chip and call are active in that order, and
- // notif is demoted to overflow
- assertThat(latest!!.active.size).isEqualTo(2)
+ // THEN media projection, then call, then notif1 are active
+ assertThat(latest!!.active.size).isEqualTo(3)
assertIsShareToAppChip(latest!!.active[0])
- assertIsCallChip(latest!!.active[1], callNotificationKey)
- assertThat(latest!!.overflow.size).isEqualTo(1)
- assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif")
+ assertIsCallChip(latest!!.active[1], callNotificationKey, context)
+ assertIsNotifChip(latest!!.active[2], context, notif1Icon, "notif1")
+ assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(2)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
- // WHEN the higher priority screen record chip is added
+ // WHEN the screen record chip is added, which replaces media projection
screenRecordState.value = ScreenRecordModel.Recording
+ // AND another notification is added
+ systemClock.advanceTime(2_000)
+ val notif2Icon = createStatusBarIconViewOrNull()
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = notif2Icon,
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ )
+ )
- // THEN the higher priority screen record chip and call are active in that order, and
- // media projection and notif are demoted in overflow
- assertThat(latest!!.active.size).isEqualTo(2)
+ // THEN screen record, then call, then notif2 are active
+ assertThat(latest!!.active.size).isEqualTo(3)
assertIsScreenRecordChip(latest!!.active[0])
- assertIsCallChip(latest!!.active[1], callNotificationKey)
+ assertIsCallChip(latest!!.active[1], callNotificationKey, context)
+ assertIsNotifChip(latest!!.active[2], context, notif2Icon, "notif2")
+
+ // AND notif1 and media projection is demoted in overflow
assertThat(latest!!.overflow.size).isEqualTo(2)
assertIsShareToAppChip(latest!!.overflow[0])
- assertIsNotifChip(latest!!.overflow[1], context, notifIcon, "notif")
+ assertIsNotifChip(latest!!.overflow[1], context, notif1Icon, "notif1")
assertThat(latest!!.inactive.size).isEqualTo(1)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
- // WHEN screen record and call is dropped
+ // WHEN screen record and call are dropped
screenRecordState.value = ScreenRecordModel.DoingNothing
- setNotifs(
- listOf(
- activeNotificationModel(
- key = "notif",
- statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
- )
- )
- )
+ removeOngoingCallState(callNotificationKey)
- // THEN media projection and notif remain
- assertThat(latest!!.active.size).isEqualTo(2)
+ // THEN media projection, notif2, and notif1 remain
+ assertThat(latest!!.active.size).isEqualTo(3)
assertIsShareToAppChip(latest!!.active[0])
- assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif")
+ assertIsNotifChip(latest!!.active[1], context, notif2Icon, "notif2")
+ assertIsNotifChip(latest!!.active[2], context, notif1Icon, "notif1")
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(3)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
// WHEN media projection is dropped
mediaProjectionState.value = MediaProjectionState.NotProjecting
+ // AND notif2 is dropped
+ systemClock.advanceTime(2_000)
+ activeNotificationListRepository.removeNotif("notif2")
- // THEN only notif is active
+ // THEN only notif1 is active
assertThat(latest!!.active.size).isEqualTo(1)
- assertIsNotifChip(latest!!.active[0], context, notifIcon, "notif")
+ assertIsNotifChip(latest!!.active[0], context, notif1Icon, "notif1")
assertThat(latest!!.overflow).isEmpty()
assertThat(latest!!.inactive.size).isEqualTo(4)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
index 06650f2afe58..b2b28a28ac38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
@@ -50,11 +50,11 @@ class MultiDisplayStatusBarOrchestratorStoreTest : SysuiTestCase() {
@Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index fee939df2cbb..18a2d0794f91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,8 +17,7 @@
package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
-import android.view.Display
-import android.view.mockIWindowManager
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,6 +28,8 @@ import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowContro
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -37,11 +38,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+@OptIn(ExperimentalCoroutinesApi::class)
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
@@ -52,293 +53,116 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
- private val windowManager = kosmos.mockIWindowManager
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
@Before
- fun setup() {
- whenever(windowManager.shouldShowSystemDecors(Display.DEFAULT_DISPLAY)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_1)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_2)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_3)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_4_NO_SYSTEM_DECOR)).thenReturn(false)
+ fun setUp() = runBlocking {
+ fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.addDisplay(DISPLAY_2)
}
@Test
- fun start_startsInitializersForCurrentDisplays() =
+ fun start_triggerAddDisplaySystemDecoration_startsInitializersForDisplay() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
underTest.start()
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_1).startedByCoreStartable)
- .isTrue()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
- .isTrue()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
+
expect
.that(
fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .forDisplay(displayId = DEFAULT_DISPLAY)
.startedByCoreStartable
)
- .isFalse()
- }
-
- @Test
- fun start_startsOrchestratorForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_1)!!)
- .start()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun start_startsPrivacyDotForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_1)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun start_doesNotStartLightBarControllerForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_1), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun start_createsLightBarControllerForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, DISPLAY_2)
- }
-
- @Test
- fun start_doesNotStartPrivacyDotForDefaultDisplay() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
-
- underTest.start()
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
- .start()
- }
-
- @Test
- fun displayAdded_orchestratorForNewDisplay() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun displayAdded_initializerForNewDisplay() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
.isTrue()
expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
+ .isTrue()
}
@Test
- fun displayAdded_privacyDotForNewDisplay() =
+ fun start_triggerAddDisplaySystemDecoration_startsOrchestratorForDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ verify(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DEFAULT_DISPLAY
+ )!!
+ )
+ .start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
.start()
}
@Test
- fun displayAdded_lightBarForNewDisplayCreate() =
+ fun start_triggerAddDisplaySystemDecoration_startsPrivacyDotForNonDefaultDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
}
@Test
- fun displayAdded_lightBarForNewDisplayStart() =
+ fun start_triggerAddDisplaySystemDecoration_doesNotStartPrivacyDotForDefaultDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DEFAULT_DISPLAY), never()).start()
}
@Test
- fun displayAddedDuringStart_initializerForNewDisplay() =
+ fun start_triggerAddDisplaySystemDecoration_doesNotStartLightBarControllerForDisplays() =
testScope.runTest {
underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
- .isTrue()
- expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
- }
-
- @Test
- fun displayAddedDuringStart_orchestratorForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun displayAddedDuringStart_privacyDotForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ verify(fakeLightBarStore.forDisplay(displayId = DEFAULT_DISPLAY), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
}
@Test
- fun displayAddedDuringStart_lightBarForNewDisplayCreate() =
+ fun start_triggerAddDisplaySystemDecoration_createsLightBarControllerForDisplay() =
testScope.runTest {
underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
- }
-
- @Test
- fun displayAddedDuringStart_lightBarForNewDisplayStart() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ assertThat(fakeLightBarStore.perDisplayMocks.keys)
+ .containsExactly(DEFAULT_DISPLAY, DISPLAY_2)
}
companion object {
- const val DISPLAY_1 = 1
const val DISPLAY_2 = 2
- const val DISPLAY_3 = 3
- const val DISPLAY_4_NO_SYSTEM_DECOR = 4
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
index 884c35c3457d..500332fa4a26 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -65,11 +65,11 @@ class LightBarControllerStoreImplTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
index f37648a639df..62ead9b45adc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
@@ -62,11 +62,11 @@ class MultiDisplayDarkIconDispatcherStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
index e0a1f273aa44..486a84598410 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
@@ -65,11 +65,11 @@ class MultiDisplayStatusBarContentInsetsProviderStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
index 11fd902fc50c..2c474da082bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -59,11 +59,11 @@ class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
index a5b7fc283976..bc7d47c52531 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
@@ -36,7 +37,7 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
@@ -58,11 +59,11 @@ class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DISPLAY_2)!!
- kosmos.displayRepository.removeDisplay(DISPLAY_2)
+ kosmos.displayRepository.triggerRemoveSystemDecorationEvent(DISPLAY_2)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt
new file mode 100644
index 000000000000..41ae377a13f7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class StatusBarPerDisplayStoreImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val store = kosmos.fakeStatusBarPerDisplayStore
+
+ @Before
+ fun start() {
+ store.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun removeSystemDecoration_onDisplayRemovalActionInvoked() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun removeSystemDecoration_twice_onDisplayRemovalActionInvokedOnce() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_onDisplayRemovalActionIsNotInvoked() =
+ testScope.runTest {
+ store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).isEmpty()
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
index 3cc592c94678..94394ede819e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -62,11 +62,11 @@ class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
index d36dbbe8d36f..d4518e7299da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
@@ -21,9 +21,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
@@ -47,7 +48,8 @@ import platform.test.runner.parameterized.Parameters
class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val mediaControlChipInteractor by lazy { kosmos.mediaControlChipInteractor }
- private val Kosmos.underTest by Kosmos.Fixture { kosmos.mediaControlChipViewModel }
+ private val Kosmos.underTest by
+ Kosmos.Fixture { kosmos.mediaControlChipViewModelFactory.create() }
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
companion object {
@@ -62,6 +64,7 @@ class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCas
fun setUp() {
MockitoAnnotations.initMocks(this)
mediaControlChipInteractor.initialize()
+ kosmos.underTest.activateIn(kosmos.testScope)
}
init {
@@ -71,7 +74,7 @@ class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCas
@Test
fun chip_noActiveMedia_IsHidden() =
kosmos.runTest {
- val chip by collectLastValue(underTest.chip)
+ val chip = underTest.chip
assertThat(chip).isInstanceOf(PopupChipModel.Hidden::class.java)
}
@@ -79,30 +82,26 @@ class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCas
@Test
fun chip_activeMedia_IsShown() =
kosmos.runTest {
- val chip by collectLastValue(underTest.chip)
-
val userMedia = MediaData(active = true, song = "test")
updateMedia(userMedia)
- assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
+ assertThat(underTest.chip).isInstanceOf(PopupChipModel.Shown::class.java)
}
@Test
fun chip_songNameChanges_chipTextUpdated() =
kosmos.runTest {
- val chip by collectLastValue(underTest.chip)
-
val initialSongName = "Initial Song"
val newSongName = "New Song"
val userMedia = MediaData(active = true, song = initialSongName)
updateMedia(userMedia)
- assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
- assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)
+ assertThat(underTest.chip).isInstanceOf(PopupChipModel.Shown::class.java)
+ assertThat((underTest.chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)
val updatedUserMedia = userMedia.copy(song = newSongName)
updateMedia(updatedUserMedia)
- assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
+ assertThat((underTest.chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
}
private fun updateMedia(mediaData: MediaData) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
new file mode 100644
index 000000000000..134ab9322df0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.featurepods.media.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
+import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModelFactory
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnableFlags(StatusBarPopupChips.FLAG_NAME)
+@RunWith(AndroidJUnit4::class)
+class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val underTest = kosmos.statusBarPopupChipsViewModelFactory.create()
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(kosmos.testScope)
+ }
+
+ @Test
+ fun shownPopupChips_allHidden_empty() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+ assertThat(shownPopupChips).isEmpty()
+ }
+
+ @Test
+ fun shownPopupChips_activeMedia_restHidden_mediaControlChipShown() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ Snapshot.takeSnapshot {
+ assertThat(shownPopupChips).hasSize(1)
+ assertThat(shownPopupChips.first().chipId).isEqualTo(PopupChipId.MediaControl)
+ }
+ }
+
+ @Test
+ fun shownPopupChips_mediaChipToggled_popupShown() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ Snapshot.takeSnapshot {
+ assertThat(shownPopupChips).hasSize(1)
+ val mediaChip = shownPopupChips.first()
+ assertThat(mediaChip.isPopupShown).isFalse()
+
+ mediaChip.showPopup.invoke()
+ assertThat(shownPopupChips.first().isPopupShown).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt
new file mode 100644
index 000000000000..e4b2e6c9b359
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.app.Notification
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import org.junit.Before
+
+private fun getCloseButton(row: ExpandableNotificationRow): View {
+ val contractedView = row.showingLayout?.contractedChild!!
+ return contractedView.findViewById(com.android.internal.R.id.close_button)
+}
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationCloseButtonTest : SysuiTestCase() {
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ helper = NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this)
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS)
+ fun verifyWhenFeatureDisabled() {
+ // Enable the notification row to dismiss.
+ helper.dismissibilityProvider.stub {
+ on { isDismissable(any()) } doReturn true
+ }
+
+ // By default, the close button should be gone.
+ val row = createNotificationRow()
+ val closeButton = getCloseButton(row)
+ assertThat(closeButton).isNotNull()
+ assertThat(closeButton.visibility).isEqualTo(View.GONE)
+
+ val hoverEnterEvent = MotionEvent.obtain(
+ 0/*downTime=*/,
+ 0/*eventTime=*/,
+ MotionEvent.ACTION_HOVER_ENTER,
+ 0f/*x=*/,
+ 0f/*y=*/,
+ 0/*metaState*/
+ )
+
+ // The close button should not show if the feature is disabled.
+ row.onInterceptHoverEvent(hoverEnterEvent)
+ assertThat(closeButton.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS)
+ fun verifyOnDismissableNotification() {
+ // Enable the notification row to dismiss.
+ helper.dismissibilityProvider.stub {
+ on { isDismissable(any()) } doReturn true
+ }
+
+ // By default, the close button should be gone.
+ val row = createNotificationRow()
+ val closeButton = getCloseButton(row)
+ assertThat(closeButton).isNotNull()
+ assertThat(closeButton.visibility).isEqualTo(View.GONE)
+
+ val hoverEnterEvent = MotionEvent.obtain(
+ 0/*downTime=*/,
+ 0/*eventTime=*/,
+ MotionEvent.ACTION_HOVER_ENTER,
+ 0f/*x=*/,
+ 0f/*y=*/,
+ 0/*metaState*/
+ )
+
+ // When the row is hovered, the close button should show.
+ row.onInterceptHoverEvent(hoverEnterEvent)
+ assertThat(closeButton.visibility).isEqualTo(View.VISIBLE)
+
+ val hoverExitEvent = MotionEvent.obtain(
+ 0/*downTime=*/,
+ 0/*eventTime=*/,
+ MotionEvent.ACTION_HOVER_EXIT,
+ 0f/*x=*/,
+ 0f/*y=*/,
+ 0/*metaState*/
+ )
+
+ // When hover exits the row, the close button should be gone again.
+ row.onInterceptHoverEvent(hoverExitEvent)
+ assertThat(closeButton.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS)
+ fun verifyOnUndismissableNotification() {
+ // By default, the close button should be gone.
+ val row = createNotificationRow()
+ val closeButton = getCloseButton(row)
+ assertThat(closeButton).isNotNull()
+ assertThat(closeButton.visibility).isEqualTo(View.GONE)
+
+ val hoverEnterEvent = MotionEvent.obtain(
+ 0/*downTime=*/,
+ 0/*eventTime=*/,
+ MotionEvent.ACTION_HOVER_ENTER,
+ 0f/*x=*/,
+ 0f/*y=*/,
+ 0/*metaState*/
+ )
+
+ // Because the host notification cannot be dismissed, the close button should not show.
+ row.onInterceptHoverEvent(hoverEnterEvent)
+ assertThat(closeButton.visibility).isEqualTo(View.GONE)
+ }
+
+ private fun createNotificationRow(): ExpandableNotificationRow {
+ val notification = Notification.Builder(context, "channel")
+ .setContentTitle("title")
+ .setContentText("text")
+ .setSmallIcon(R.drawable.ic_person)
+ .build()
+
+ return helper.createRow(notification)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 0f1cdac05f7a..fdc6657acd1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -31,6 +31,8 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.domain.interactor.enableDualShade
+import com.android.systemui.shade.domain.interactor.enableSingleShade
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN
@@ -69,6 +71,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
@Test
fun updateBounds() =
testScope.runTest {
+ kosmos.enableSingleShade()
val radius = MutableStateFlow(32)
val leftOffset = MutableStateFlow(0)
val shape by
@@ -106,7 +109,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
)
)
- // When: QuickSettings shows up full screen
+ // When: QuickSettings shows up full screen on single shade.
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -115,6 +118,10 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
sceneInteractor.setTransitionState(transitionState)
// Then: shape is null
assertThat(shape).isNull()
+
+ // Same scenario on Dual Shade, shape should have clipping bounds
+ kosmos.enableDualShade()
+ assertThat(shape).isNotNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
index 83fd5dcd5f82..a13b8647e170 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
@@ -34,123 +34,122 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
-class BundleEntryTest : SysuiTestCase() {
- private lateinit var underTest: BundleEntry
+class BundleEntryAdapterTest : SysuiTestCase() {
+ private lateinit var underTest: BundleEntryAdapter
- @get:Rule
- val setFlagsRule = SetFlagsRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
@Before
fun setUp() {
- underTest = BundleEntry("key")
+ underTest = BundleEntryAdapter(BundleEntry("key"))
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getParent_adapter() {
- assertThat(underTest.entryAdapter.parent).isEqualTo(GroupEntry.ROOT_ENTRY)
+ assertThat(underTest.parent).isEqualTo(GroupEntry.ROOT_ENTRY)
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isTopLevelEntry_adapter() {
- assertThat(underTest.entryAdapter.isTopLevelEntry).isTrue()
+ assertThat(underTest.isTopLevelEntry).isTrue()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getRow_adapter() {
- assertThat(underTest.entryAdapter.row).isNull()
+ assertThat(underTest.row).isNull()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupRoot_adapter() {
- assertThat(underTest.entryAdapter.isGroupRoot).isTrue()
+ assertThat(underTest.isGroupRoot).isTrue()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getKey_adapter() {
- assertThat(underTest.entryAdapter.key).isEqualTo("key")
+ assertThat(underTest.key).isEqualTo("key")
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isClearable_adapter() {
- assertThat(underTest.entryAdapter.isClearable).isTrue()
+ assertThat(underTest.isClearable).isTrue()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSummarization_adapter() {
- assertThat(underTest.entryAdapter.summarization).isNull()
+ assertThat(underTest.summarization).isNull()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getContrastedColor_adapter() {
- assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE))
+ assertThat(underTest.getContrastedColor(context, false, Color.WHITE))
.isEqualTo(Notification.COLOR_DEFAULT)
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canPeek_adapter() {
- assertThat(underTest.entryAdapter.canPeek()).isFalse()
+ assertThat(underTest.canPeek()).isFalse()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getWhen_adapter() {
- assertThat(underTest.entryAdapter.`when`).isEqualTo(0)
+ assertThat(underTest.`when`).isEqualTo(0)
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isColorized() {
- assertThat(underTest.entryAdapter.isColorized).isFalse()
+ assertThat(underTest.isColorized).isFalse()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSbn() {
- assertThat(underTest.entryAdapter.sbn).isNull()
+ assertThat(underTest.sbn).isNull()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canDragAndDrop() {
- assertThat(underTest.entryAdapter.canDragAndDrop()).isFalse()
+ assertThat(underTest.canDragAndDrop()).isFalse()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isBubble() {
- assertThat(underTest.entryAdapter.isBubbleCapable).isFalse()
+ assertThat(underTest.isBubbleCapable).isFalse()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getStyle() {
- assertThat(underTest.entryAdapter.style).isNull()
+ assertThat(underTest.style).isNull()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSectionBucket() {
- assertThat(underTest.entryAdapter.sectionBucket).isEqualTo(underTest.bucket)
+ assertThat(underTest.sectionBucket).isEqualTo(underTest.entry.bucket)
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isAmbient() {
- assertThat(underTest.entryAdapter.isAmbient).isFalse()
+ assertThat(underTest.isAmbient).isFalse()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canShowFullScreen() {
- assertThat(underTest.entryAdapter.isFullScreenCapable()).isFalse()
+ assertThat(underTest.isFullScreenCapable()).isFalse()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
new file mode 100644
index 000000000000..faafa073be4c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.app.ActivityManager
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.mockNotificationActivityStarter
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.entryAdapterFactory
+import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class NotificationEntryAdapterTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory
+ private lateinit var underTest: NotificationEntryAdapter
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getParent_adapter() {
+ val ge = GroupEntryBuilder().build()
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(ge)
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.parent).isEqualTo(entry.parent)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isTopLevelEntry_adapter() {
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isTopLevelEntry).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getKey_adapter() {
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.key).isEqualTo(entry.key)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getRow_adapter() {
+ val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .build()
+ entry.row = row
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.row).isEqualTo(entry.row)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isGroupRoot_adapter_groupSummary() {
+ val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroupSummary(true)
+ .setGroup("key")
+ .build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build()
+ entry.row = row
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isGroupRoot).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isGroupRoot_adapter_groupChild() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroupSummary(true)
+ .setGroup("key")
+ .build()
+
+ val parent = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ val groupEntry = GroupEntryBuilder().setSummary(parent)
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .setParent(groupEntry.build())
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isGroupRoot).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isClearable_adapter() {
+ val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .build()
+ entry.row = row
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isClearable).isEqualTo(entry.isClearable)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSummarization_adapter() {
+ val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .build()
+ val ranking = RankingBuilder(entry.ranking).setSummarization("hello").build()
+ entry.setRanking(ranking)
+ entry.row = row
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.summarization).isEqualTo("hello")
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getIcons_adapter() {
+ val row = Mockito.mock(ExpandableNotificationRow::class.java)
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setUser(UserHandle(ActivityManager.getCurrentUser()))
+ .build()
+ entry.row = row
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.icons).isEqualTo(entry.icons)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isColorized() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setColorized(true)
+ .build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isColorized).isEqualTo(entry.sbn.notification.isColorized)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSbn() {
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.sbn).isEqualTo(entry.sbn)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun canDragAndDrop() {
+ val pi = Mockito.mock(PendingIntent::class.java)
+ Mockito.`when`(pi.isActivity).thenReturn(true)
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentIntent(pi)
+ .build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.canDragAndDrop()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isBubble() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFlag(Notification.FLAG_BUBBLE, true)
+ .build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isBubbleCapable).isEqualTo(entry.isBubble)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getStyle() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(Notification.BigTextStyle())
+ .build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.style).isEqualTo(entry.notificationStyle)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSectionBucket() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(Notification.BigTextStyle())
+ .build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+ entry.bucket = BUCKET_ALERTING
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.sectionBucket).isEqualTo(BUCKET_ALERTING)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isAmbient() {
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(NotificationManager.IMPORTANCE_MIN)
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isAmbient).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun canShowFullScreen() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(Mockito.mock(PendingIntent::class.java), true)
+ .build()
+
+ val entry =
+ NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(NotificationManager.IMPORTANCE_MIN)
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.isFullScreenCapable).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun onNotificationBubbleIconClicked() {
+ val notification: Notification =
+ Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+
+ underTest.onNotificationBubbleIconClicked()
+ verify(kosmos.mockNotificationActivityStarter).onNotificationBubbleIconClicked(entry)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun onNotificationActionClicked() {
+ val notification: Notification =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .addAction(Mockito.mock(Notification.Action::class.java))
+ .build()
+
+ val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ underTest.onNotificationActionClicked()
+ verify(kosmos.mockNotificationActionClickManager).onNotificationActionClicked(entry)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 481081344dbb..790b2c343a11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,24 +21,17 @@ import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
import static android.app.Notification.FLAG_PROMOTED_ONGOING;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
-
-import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
@@ -66,8 +59,6 @@ import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -458,336 +449,6 @@ public class NotificationEntryTest extends SysuiTestCase {
// no crash, good
}
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getParent_adapter() {
- GroupEntry ge = new GroupEntryBuilder()
- .build();
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .setParent(ge)
- .build();
-
- assertThat(entry.getEntryAdapter().getParent()).isEqualTo(entry.getParent());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isTopLevelEntry_adapter() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .setParent(GroupEntry.ROOT_ENTRY)
- .build();
-
- assertThat(entry.getEntryAdapter().isTopLevelEntry()).isTrue();
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getKey_adapter() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
-
- assertThat(entry.getEntryAdapter().getKey()).isEqualTo(entry.getKey());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getRow_adapter() {
- ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- entry.setRow(row);
-
- assertThat(entry.getEntryAdapter().getRow()).isEqualTo(entry.getRow());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isGroupRoot_adapter_groupSummary() {
- ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setGroupSummary(true)
- .setGroup("key")
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .setParent(GroupEntry.ROOT_ENTRY)
- .build();
- entry.setRow(row);
-
- assertThat(entry.getEntryAdapter().isGroupRoot()).isFalse();
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isGroupRoot_adapter_groupChild() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setGroupSummary(true)
- .setGroup("key")
- .build();
-
- NotificationEntry parent = new NotificationEntryBuilder()
- .setParent(GroupEntry.ROOT_ENTRY)
- .build();
- GroupEntryBuilder groupEntry = new GroupEntryBuilder()
- .setSummary(parent);
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .setParent(groupEntry.build())
- .build();
-
- assertThat(entry.getEntryAdapter().isGroupRoot()).isFalse();
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isClearable_adapter() {
- ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- entry.setRow(row);
-
- assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getSummarization_adapter() {
- ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- Ranking ranking = new RankingBuilder(entry.getRanking())
- .setSummarization("hello")
- .build();
- entry.setRanking(ranking);
- entry.setRow(row);
-
- assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello");
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getIcons_adapter() {
- ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setChannel(mChannel)
- .setId(mId++)
- .setNotification(notification)
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- entry.setRow(row);
-
- assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isColorized() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setColorized(true)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
- assertThat(entry.getEntryAdapter().isColorized()).isEqualTo(
- entry.getSbn().getNotification().isColorized());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getSbn() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
- assertThat(entry.getEntryAdapter().getSbn()).isEqualTo(
- entry.getSbn());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void canDragAndDrop() {
- PendingIntent pi = mock(PendingIntent.class);
- when(pi.isActivity()).thenReturn(true);
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setContentIntent(pi)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
- assertThat(entry.getEntryAdapter().canDragAndDrop()).isTrue();
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isBubble() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFlag(FLAG_BUBBLE, true)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
- assertThat(entry.getEntryAdapter().isBubbleCapable()).isEqualTo(entry.isBubble());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getStyle() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setStyle(new Notification.BigTextStyle())
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
- assertThat(entry.getEntryAdapter().getStyle()).isEqualTo(entry.getNotificationStyle());
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void getSectionBucket() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setStyle(new Notification.BigTextStyle())
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
- entry.setBucket(BUCKET_ALERTING);
-
- assertThat(entry.getEntryAdapter().getSectionBucket()).isEqualTo(BUCKET_ALERTING);
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void isAmbient() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .setImportance(IMPORTANCE_MIN)
- .build();
-
- assertThat(entry.getEntryAdapter().isAmbient()).isTrue();
- }
-
- @Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
- public void canShowFullScreen() {
- Notification notification = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(mock(PendingIntent.class), true)
- .build();
-
- NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .setImportance(IMPORTANCE_MIN)
- .build();
-
- assertThat(entry.getEntryAdapter().isFullScreenCapable()).isTrue();
- }
-
private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
index 8a9720ea3cb0..732180810880 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
@@ -34,6 +34,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -87,6 +88,18 @@ class BundleCoordinatorTest : SysuiTestCase() {
isFalse()
}
+ @Test
+ fun testBundler_getBundleIdOrNull_returnBundleId() {
+ val classifiedEntry = makeEntryOfChannelType(PROMOTIONS_ID)
+ assertEquals(coordinator.bundler.getBundleIdOrNull(classifiedEntry), PROMOTIONS_ID)
+ }
+
+ @Test
+ fun testBundler_getBundleIdOrNull_returnNull() {
+ val unclassifiedEntry = makeEntryOfChannelType("not system channel")
+ assertEquals(coordinator.bundler.getBundleIdOrNull(unclassifiedEntry), null)
+ }
+
private fun makeEntryOfChannelType(
type: String,
buildBlock: NotificationEntryBuilder.() -> Unit = {}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 609885d0214b..44d88c31c5f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -50,6 +50,8 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
+import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
@@ -138,6 +140,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
headsUpViewBinder,
visualInterruptionDecisionProvider,
remoteInputManager,
+ kosmos.mockNotificationActionClickManager,
launchFullScreenIntentProvider,
flags,
statusBarNotificationChipsInteractor,
@@ -161,8 +164,14 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture())
}
onHeadsUpChangedListener = withArgCaptor { verify(headsUpManager).addListener(capture()) }
- actionPressListener = withArgCaptor {
- verify(remoteInputManager).addActionPressListener(capture())
+ actionPressListener = if (NotificationBundleUi.isEnabled) {
+ withArgCaptor {
+ verify(kosmos.mockNotificationActionClickManager).addActionClickListener(capture())
+ }
+ } else {
+ withArgCaptor {
+ verify(remoteInputManager).addActionPressListener(capture())
+ }
}
given(headsUpManager.allEntries).willAnswer { huns.stream() }
given(headsUpManager.isHeadsUpEntry(anyString())).willAnswer { invocation ->
@@ -260,7 +269,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
addHUN(entry)
actionPressListener.accept(entry)
- verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry)
+ verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry.key)
whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true)
assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
@@ -549,7 +558,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTap() =
+ fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTapImmediately() =
testScope.runTest {
whenever(notifCollection.getEntry(entry.key)).thenReturn(entry)
@@ -570,8 +579,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
executor.runAllReady()
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
- // THEN HUN is hidden
- verify(headsUpManager).removeNotification(eq(entry.key), eq(false), any())
+ // THEN HUN is hidden and it's hidden immediately
+ verify(headsUpManager)
+ .removeNotification(eq(entry.key), /* releaseImmediately= */ eq(true), any())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index a90539413adb..e28e587d2cdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.BundleEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -280,6 +281,8 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build()
val listEntryList = listOf(group, solo1, solo2)
val notificationEntryList = listOf(solo1, solo2, parent, child1, child2)
+ val bundle = BundleEntry("bundleKey")
+ val bundleList = listOf(bundle)
runCoordinatorTest {
// All entries are added (and now unseen)
@@ -300,6 +303,11 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
assertThatTopOngoingKey().isEqualTo(null)
assertThatTopUnseenKey().isEqualTo(solo1.key)
+ // TEST: bundle is not picked
+ onBeforeTransformGroupsListener.onBeforeTransformGroups(bundleList)
+ assertThatTopOngoingKey().isEqualTo(null)
+ assertThatTopUnseenKey().isEqualTo(null)
+
// TEST: if top-ranked unseen is colorized, fall back to #2 ranked unseen
solo1.setColorizedFgs(true)
onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index c5752691da44..f9405af3f85d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
@@ -45,6 +47,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.collection.BundleEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -273,6 +276,18 @@ public class RankingCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testSilentSectioner_acceptsBundle() {
+ BundleEntry bundleEntry = new BundleEntry("testBundleKey");
+ assertTrue(mSilentSectioner.isInSection(bundleEntry));
+ }
+
+ @Test
+ public void testMinimizedSectioner_rejectsBundle() {
+ BundleEntry bundleEntry = new BundleEntry("testBundleKey");
+ assertFalse(mMinimizedSectioner.isInSection(bundleEntry));
+ }
+
+ @Test
public void testMinSection() {
when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
setRankingAmbient(true);
@@ -327,6 +342,13 @@ public class RankingCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testAlertingSectioner_rejectsBundle() {
+ for (String id : SYSTEM_RESERVED_IDS) {
+ assertFalse(mAlertingSectioner.isInSection(makeClassifiedNotifEntry(id)));
+ }
+ }
+
+ @Test
public void statusBarStateCallbackTest() {
mStatusBarStateCallback.onDozeAmountChanged(1f, 1f);
verify(mInvalidationListener, times(1))
@@ -379,4 +401,11 @@ public class RankingCoordinatorTest extends SysuiTestCase {
.build());
assertEquals(ambient, mEntry.getRanking().isAmbient());
}
+
+ private NotificationEntry makeClassifiedNotifEntry(String channelId) {
+ NotificationChannel channel = new NotificationChannel(channelId, channelId, IMPORTANCE_LOW);
+ return new NotificationEntryBuilder()
+ .updateRanking((rankingBuilder -> rankingBuilder.setChannel(channel)))
+ .build();
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 8e6aedcae506..9e27c79d54b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.collection.render
-import android.os.Build
-import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -26,6 +24,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.assertLogsWtf
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -36,12 +35,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.row.entryAdapterFactory
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -54,11 +54,11 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class GroupExpansionManagerTest : SysuiTestCase() {
- @get:Rule
- val setFlagsRule = SetFlagsRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
private lateinit var underTest: GroupExpansionManagerImpl
+ private val kosmos = testKosmos()
private lateinit var testHelper: NotificationTestHelper
private val dumpManager: DumpManager = mock()
private val groupMembershipManager: GroupMembershipManager = mock()
@@ -66,15 +66,14 @@ class GroupExpansionManagerTest : SysuiTestCase() {
private val pipeline: NotifPipeline = mock()
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+ private val factory: EntryAdapterFactoryImpl = kosmos.entryAdapterFactory
private lateinit var summary1: NotificationEntry
private lateinit var summary2: NotificationEntry
private lateinit var entries: List<ListEntry>
private fun notificationEntry(pkg: String, id: Int, parent: ExpandableNotificationRow?) =
NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply {
- row = testHelper.createRow().apply {
- setIsChildInGroup(true, parent)
- }
+ row = testHelper.createRow().apply { setIsChildInGroup(true, parent) }
}
@Before
@@ -91,7 +90,7 @@ class GroupExpansionManagerTest : SysuiTestCase() {
listOf(
notificationEntry("foo", 2, summary1.row),
notificationEntry("foo", 3, summary1.row),
- notificationEntry("foo", 4, summary1.row)
+ notificationEntry("foo", 4, summary1.row),
)
)
.build(),
@@ -101,11 +100,11 @@ class GroupExpansionManagerTest : SysuiTestCase() {
listOf(
notificationEntry("bar", 2, summary2.row),
notificationEntry("bar", 3, summary2.row),
- notificationEntry("bar", 4, summary2.row)
+ notificationEntry("bar", 4, summary2.row),
)
)
.build(),
- notificationEntry("baz", 1, null)
+ notificationEntry("baz", 1, null),
)
whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
@@ -135,18 +134,20 @@ class GroupExpansionManagerTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun notifyOnlyOnChange_withEntryAdapter() {
+ val entryAdapter1 = factory.create(summary1)
+ val entryAdapter2 = factory.create(summary2)
var listenerCalledCount = 0
underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- underTest.setGroupExpanded(summary1.entryAdapter, false)
+ underTest.setGroupExpanded(entryAdapter1, false)
assertThat(listenerCalledCount).isEqualTo(0)
- underTest.setGroupExpanded(summary1.entryAdapter, true)
+ underTest.setGroupExpanded(entryAdapter1, true)
assertThat(listenerCalledCount).isEqualTo(1)
- underTest.setGroupExpanded(summary2.entryAdapter, true)
+ underTest.setGroupExpanded(entryAdapter2, true)
assertThat(listenerCalledCount).isEqualTo(2)
- underTest.setGroupExpanded(summary1.entryAdapter, true)
+ underTest.setGroupExpanded(entryAdapter1, true)
assertThat(listenerCalledCount).isEqualTo(2)
- underTest.setGroupExpanded(summary2.entryAdapter, false)
+ underTest.setGroupExpanded(entryAdapter2, false)
assertThat(listenerCalledCount).isEqualTo(3)
}
@@ -168,16 +169,17 @@ class GroupExpansionManagerTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun expandUnattachedEntryAdapter() {
+ val entryAdapter = factory.create(summary1)
// First, expand the entry when it is attached.
- underTest.setGroupExpanded(summary1.entryAdapter, true)
- assertThat(underTest.isGroupExpanded(summary1.entryAdapter)).isTrue()
+ underTest.setGroupExpanded(entryAdapter, true)
+ assertThat(underTest.isGroupExpanded(entryAdapter)).isTrue()
// Un-attach it, and un-expand it.
NotificationEntryBuilder.setNewParent(summary1, null)
- underTest.setGroupExpanded(summary1.entryAdapter, false)
+ underTest.setGroupExpanded(entryAdapter, false)
// Expanding again should throw.
- assertLogsWtf { underTest.setGroupExpanded(summary1.entryAdapter, true) }
+ assertLogsWtf { underTest.setGroupExpanded(entryAdapter, true) }
}
@Test
@@ -207,6 +209,7 @@ class GroupExpansionManagerTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun syncWithPipeline_withEntryAdapter() {
+ val entryAdapter = factory.create(summary1)
underTest.attach(pipeline)
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
@@ -219,7 +222,7 @@ class GroupExpansionManagerTest : SysuiTestCase() {
verify(listener, never()).onGroupExpansionChange(any(), any())
// Expand one of the groups.
- underTest.setGroupExpanded(summary1.entryAdapter, true)
+ underTest.setGroupExpanded(entryAdapter, true)
verify(listener).onGroupExpansionChange(summary1.row, true)
// Empty the pipeline list and verify that the group is no longer expanded.
@@ -231,11 +234,15 @@ class GroupExpansionManagerTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupExpanded() {
- underTest.setGroupExpanded(summary1.entryAdapter, true)
-
- assertThat(underTest.isGroupExpanded(summary1.entryAdapter)).isTrue();
- assertThat(underTest.isGroupExpanded(
- (entries[0] as? GroupEntry)?.getChildren()?.get(0)?.entryAdapter))
- .isTrue();
+ val entryAdapter = summary1.row.entryAdapter
+ underTest.setGroupExpanded(entryAdapter, true)
+
+ assertThat(underTest.isGroupExpanded(entryAdapter)).isTrue()
+ assertThat(
+ underTest.isGroupExpanded(
+ (entries[0] as? GroupEntry)?.getChildren()?.get(0)?.row?.entryAdapter
+ )
+ )
+ .isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
index 2bbf094021cf..0ccf58507696 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -22,10 +22,13 @@ import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.row.entryAdapterFactory
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -35,8 +38,10 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class GroupMembershipManagerTest : SysuiTestCase() {
- @get:Rule
- val setFlagsRule = SetFlagsRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val kosmos = testKosmos()
+ private val factory: EntryAdapterFactoryImpl = kosmos.entryAdapterFactory
private var underTest = GroupMembershipManagerImpl()
@@ -144,14 +149,14 @@ class GroupMembershipManagerTest : SysuiTestCase() {
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isChildEntryAdapterInGroup_topLevel() {
val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(underTest.isChildInGroup(topLevelEntry.entryAdapter)).isFalse()
+ assertThat(underTest.isChildInGroup(factory.create(topLevelEntry))).isFalse()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isChildEntryAdapterInGroup_noParent() {
val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(underTest.isChildInGroup(noParentEntry.entryAdapter)).isFalse()
+ assertThat(underTest.isChildInGroup(factory.create(noParentEntry))).isFalse()
}
@Test
@@ -165,7 +170,7 @@ class GroupMembershipManagerTest : SysuiTestCase() {
.build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
- assertThat(underTest.isChildInGroup(summary.entryAdapter)).isFalse()
+ assertThat(underTest.isChildInGroup(factory.create(summary))).isFalse()
}
@Test
@@ -180,14 +185,14 @@ class GroupMembershipManagerTest : SysuiTestCase() {
val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
- assertThat(underTest.isChildInGroup(entry.entryAdapter)).isTrue()
+ assertThat(underTest.isChildInGroup(factory.create(entry))).isTrue()
}
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupRoot_topLevelEntry() {
val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(underTest.isGroupRoot(entry.entryAdapter)).isFalse()
+ assertThat(underTest.isGroupRoot(factory.create(entry))).isFalse()
}
@Test
@@ -201,7 +206,7 @@ class GroupMembershipManagerTest : SysuiTestCase() {
.build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
- assertThat(underTest.isGroupRoot(summary.entryAdapter)).isTrue()
+ assertThat(underTest.isGroupRoot(factory.create(summary))).isTrue()
}
@Test
@@ -216,6 +221,6 @@ class GroupMembershipManagerTest : SysuiTestCase() {
val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
- assertThat(underTest.isGroupRoot(entry.entryAdapter)).isFalse()
+ assertThat(underTest.isGroupRoot(factory.create(entry))).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
index dc27859df421..a2e4a328697e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
@@ -17,9 +17,10 @@ package com.android.systemui.statusbar.notification.headsup
import android.app.Notification
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper.RunWithLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
@@ -53,12 +55,18 @@ import org.mockito.Mockito
import org.mockito.invocation.InvocationOnMock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWithLooper
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
-class AvalancheControllerTest : SysuiTestCase() {
+class AvalancheControllerTest(val flags: FlagsParameterization) : SysuiTestCase() {
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos = testKosmos()
// For creating mocks
@@ -72,10 +80,10 @@ class AvalancheControllerTest : SysuiTestCase() {
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
- @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
+ private val headsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
@Mock private lateinit var mBgHandler: Handler
- private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mLogger = Mockito.spy(headsUpManagerLogger)
private val mGlobalSettings = FakeGlobalSettings()
private val mSystemClock = FakeSystemClock()
private val mExecutor = FakeExecutor(mSystemClock)
@@ -95,7 +103,7 @@ class AvalancheControllerTest : SysuiTestCase() {
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
mAvalancheController =
- AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
+ AvalancheController(dumpManager, mUiEventLoggerFake, headsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
HeadsUpManagerImpl(
@@ -278,7 +286,7 @@ class AvalancheControllerTest : SysuiTestCase() {
// Delete
mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
- // Next entry is shown
+ // Showing entry becomes previous
assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
}
@@ -296,12 +304,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Delete
mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
- // Next entry is shown
+ // Previous key not filled in
assertThat(mAvalancheController.previousHunKey).isEqualTo("")
}
@Test
- fun testGetDurationMs_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
+ fun testGetDuration_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
val givenEntry = createHeadsUpEntry(id = 0)
// Nothing is showing
@@ -310,12 +318,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Nothing is next
mAvalancheController.clearNext()
- val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(givenEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
+ fun testGetDuration_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
val givenEntry = createHeadsUpEntry(id = 0)
// Given entry not tracked
@@ -325,12 +333,12 @@ class AvalancheControllerTest : SysuiTestCase() {
val nextEntry = createHeadsUpEntry(id = 2)
mAvalancheController.addToNext(nextEntry, runnableMock!!)
- val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(givenEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_lastEntry_useAutoDismissTime() {
+ fun testGetDuration_lastEntry_useAutoDismissTime() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -338,12 +346,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Nothing is next
mAvalancheController.clearNext()
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_nextEntryLowerPriority_5000() {
+ fun testGetDuration_nextEntryLowerPriority_5000() {
// Entry is showing
val showingEntry = createFsiHeadsUpEntry(id = 1)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -355,12 +363,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Next entry has lower priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_nextEntrySamePriority_1000() {
+ fun testGetDuration_nextEntrySamePriority_1000() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -372,12 +380,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Same priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(1000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(1000)
}
@Test
- fun testGetDurationMs_nextEntryHigherPriority_500() {
+ fun testGetDuration_nextEntryHigherPriority_500() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -389,7 +397,51 @@ class AvalancheControllerTest : SysuiTestCase() {
// Next entry has higher priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(500)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(500)
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testGetDuration_nextEntryIsPinnedByUser_flagOff_1000() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next and it's PinnedByUser
+ val nextEntry = createHeadsUpEntry(id = 1)
+ nextEntry.requestedPinnedStatus = PinnedStatus.PinnedByUser
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+
+ // BUT PinnedByUser is ignored because flag is off, so the duration for a SAME priority next
+ // is used
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(1000)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testGetDuration_nextEntryIsPinnedByUser_flagOn_hideImmediately() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next and it's PinnedByUser
+ val nextEntry = createHeadsUpEntry(id = 1)
+ nextEntry.requestedPinnedStatus = PinnedStatus.PinnedByUser
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ val duration = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+
+ assertThat(duration).isEqualTo(RemainingDuration.HideImmediately)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(StatusBarNotifChips.FLAG_NAME)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
index 206eb89db94f..706885bf5dee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
@@ -21,6 +21,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
@@ -30,6 +32,8 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
class HeadsUpAnimatorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
context.getOrCreateTestableResources().apply {
@@ -38,34 +42,64 @@ class HeadsUpAnimatorTest : SysuiTestCase() {
}
@Test
- fun getHeadsUpYTranslation_fromBottomTrue_usesBottomAndYAbove() {
- val underTest = HeadsUpAnimator(context)
+ fun getHeadsUpYTranslation_fromBottomTrue_hasStatusBarChipFalse_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = false)
+
+ assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
+ }
+
+ @Test
+ fun getHeadsUpYTranslation_fromBottomTrue_hasStatusBarChipTrue_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = true)
+ // fromBottom takes priority
assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
}
@Test
- fun getHeadsUpYTranslation_fromBottomFalse_usesTopMarginAndYAbove() {
- val underTest = HeadsUpAnimator(context)
+ fun getHeadsUpYTranslation_fromBottomFalse_hasStatusBarChipFalse_usesTopMarginAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(-30 - TEST_Y_ABOVE_SCREEN)
}
@Test
+ fun getHeadsUpYTranslation_fromBottomFalse_hasStatusBarChipTrue_usesTopMarginAndStatusBarHeight() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = 75
+ underTest.updateResources(context)
+
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false, hasStatusBarChip = true)
+
+ assertThat(yTranslation).isEqualTo(75 - 30)
+ }
+
+ @Test
fun getHeadsUpYTranslation_resourcesUpdated() {
- val underTest = HeadsUpAnimator(context)
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
@@ -77,7 +111,12 @@ class HeadsUpAnimatorTest : SysuiTestCase() {
underTest.updateResources(context)
// THEN HeadsUpAnimator knows about it
- assertThat(underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true))
+ assertThat(
+ underTest.getHeadsUpYTranslation(
+ isHeadsUpFromBottom = true,
+ hasStatusBarChip = false,
+ )
+ )
.isEqualTo(newYAbove + 300)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
index e22acd53e584..8560b66d961f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -37,6 +37,7 @@ import com.android.systemui.flags.andSceneContainer
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.assertLogsWtfs
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeTestUtil
@@ -205,8 +206,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
fun pinnedHeadsUpStatuses_pinnedByUser_butFlagOff_returnsNotPinned() {
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
entry.row = testHelper.createRow()
- underTest.showNotification(entry, isPinnedByUser = true)
-
+ assertLogsWtfs { underTest.showNotification(entry, isPinnedByUser = true) }
assertThat(underTest.hasPinnedHeadsUp()).isFalse()
assertThat(underTest.pinnedHeadsUpStatus()).isEqualTo(PinnedStatus.NotPinned)
}
@@ -1071,7 +1071,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(underTest.canRemoveImmediately(notifEntry.key)).isFalse()
- underTest.setUserActionMayIndirectlyRemove(notifEntry)
+ underTest.setUserActionMayIndirectlyRemove(notifEntry.key)
assertThat(underTest.canRemoveImmediately(notifEntry.key)).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 216fd2d54a1e..893c17998a17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -57,7 +57,8 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
private val underTest = kosmos.promotedNotificationContentExtractor
private val systemClock = kosmos.fakeSystemClock
- private val rowImageInflater = RowImageInflater.newInstance(previousIndex = null)
+ private val rowImageInflater =
+ RowImageInflater.newInstance(previousIndex = null, reinflating = false)
private val imageModelProvider by lazy { rowImageInflater.useForContentModel() }
@Test
@@ -375,12 +376,67 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromBigTextStyle() {
- val entry = createEntry { setStyle(BigTextStyle()) }
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ .bigText(TEST_BIG_TEXT)
+ .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.BigText)
+ assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromBigTextStyle_fallbackToContentTitle() {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ .bigText(TEST_BIG_TEXT)
+ // bigContentTitle unset
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.BigText)
+ assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromBigTextStyle_fallbackToContentText() {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ // bigText unset
+ .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
val content = extractContent(entry)
assertThat(content).isNotNull()
assertThat(content?.style).isEqualTo(Style.BigText)
+ assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
}
@Test
@@ -497,6 +553,10 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
private const val TEST_CONTENT_TEXT = "content text"
private const val TEST_SHORT_CRITICAL_TEXT = "short"
+ private const val TEST_BIG_CONTENT_TITLE = "big content title"
+ private const val TEST_BIG_TEXT = "big text"
+ private const val TEST_SUMMARY_TEXT = "summary text"
+
private const val TEST_PROGRESS = 50
private const val TEST_PROGRESS_MAX = 100
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
index aa6e76d08c17..6926677feda0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -111,7 +111,7 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
)
val topPromotedNotificationContent by
- collectLastValue(underTest.topPromotedNotificationContent)
+ collectLastValue(underTest.aodPromotedNotification)
// THEN the ron is first because the call has no content
assertThat(topPromotedNotificationContent?.identity?.key)
@@ -131,7 +131,7 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
)
val topPromotedNotificationContent by
- collectLastValue(underTest.topPromotedNotificationContent)
+ collectLastValue(underTest.aodPromotedNotification)
// THEN the call is the top notification
assertThat(topPromotedNotificationContent?.identity?.key)
@@ -148,7 +148,7 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
renderNotificationListInteractor.setRenderedList(listOf(callEntry, otherEntry))
val topPromotedNotificationContent by
- collectLastValue(underTest.topPromotedNotificationContent)
+ collectLastValue(underTest.aodPromotedNotification)
// THEN there is no top promoted notification
assertThat(topPromotedNotificationContent).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index f060caea4f24..bb12eff51288 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -37,9 +37,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.ColorUpdateLogger
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.EntryAdapter
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
@@ -47,6 +50,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -112,6 +116,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
private val uiEventLogger: UiEventLogger = mock()
private val msdlPlayer: MSDLPlayer = mock()
private val rebindingTracker: NotificationRebindingTracker = mock()
+ private val entryAdapterFactory: EntryAdapterFactory = mock()
private lateinit var controller: ExpandableNotificationRowController
@Before
@@ -154,6 +159,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
uiEventLogger,
msdlPlayer,
rebindingTracker,
+ entryAdapterFactory,
)
whenever(view.childrenContainer).thenReturn(childrenContainer)
@@ -277,8 +283,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
whenever(view.privateLayout).thenReturn(childView)
val entryAdapter = mock(EntryAdapter::class.java)
val sbn =
- SbnBuilder().setNotification(Notification.Builder(mContext).build())
- .setUser(UserHandle.of(view.entry.sbn.userId)).build()
+ SbnBuilder()
+ .setNotification(Notification.Builder(mContext).build())
+ .setUser(UserHandle.of(view.entry.sbn.userId))
+ .build()
whenever(entryAdapter.sbn).thenReturn(sbn)
whenever(view.entryAdapter).thenReturn(entryAdapter)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 9f35d631bd45..99f2596dbf1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
@@ -472,7 +472,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
com.android.systemui.res.R.drawable.ic_person).setStyle(messagingStyle).build();
ExpandableNotificationRow row = mHelper.createRow(messageNotif);
inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC,
- REDACTION_TYPE_SENSITIVE_CONTENT, row);
+ REDACTION_TYPE_OTP, row);
NotificationContentView publicView = row.getPublicLayout();
assertNotNull(publicView);
// The display name should be included, but not the content or message text
@@ -493,7 +493,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
.build();
ExpandableNotificationRow row = mHelper.createRow(notif);
inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC,
- REDACTION_TYPE_SENSITIVE_CONTENT, row);
+ REDACTION_TYPE_OTP, row);
NotificationContentView publicView = row.getPublicLayout();
assertNotNull(publicView);
assertFalse(hasText(publicView, contentText));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 1b8d64d5483c..387c62d76083 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.row.icon.appIconProvider
@@ -80,6 +81,9 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlin.test.fail
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -107,9 +111,6 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-import java.util.Optional
-import kotlin.test.assertNotNull
-import kotlin.test.fail
/** Tests for [NotificationGutsManager]. */
@SmallTest
@@ -149,6 +150,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
@Mock private lateinit var launcherApps: LauncherApps
@Mock private lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var packageDemotionInteractor: PackageDemotionInteractor
@Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
@Mock private lateinit var contextTracker: UserContextProvider
@Mock private lateinit var bubblesManager: BubblesManager
@@ -214,6 +216,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
launcherApps,
shortcutManager,
channelEditorDialogController,
+ packageDemotionInteractor,
contextTracker,
assistantFeedbackController,
Optional.of(bubblesManager),
@@ -509,6 +512,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -521,6 +525,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ any<PackageDemotionInteractor>(),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -530,6 +535,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<UiEventLogger>(),
/* isDeviceProvisioned = */ eq(false),
/* isNonblockable = */ eq(false),
+ /* isDismissable = */ eq(true),
/* wasShownHighPriority = */ eq(true),
eq(assistantFeedbackController),
eq(metricsLogger),
@@ -545,6 +551,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -560,6 +567,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ any<PackageDemotionInteractor>(),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -569,6 +577,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<UiEventLogger>(),
/* isDeviceProvisioned = */ eq(true),
/* isNonblockable = */ eq(false),
+ /* isDismissable = */ eq(true),
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
@@ -584,6 +593,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -597,6 +607,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<NotificationIconStyleProvider>(),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ any<PackageDemotionInteractor>(),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -606,6 +617,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<UiEventLogger>(),
/* isDeviceProvisioned = */ eq(false),
/* isNonblockable = */ eq(false),
+ /* isDismissable = */ eq(true),
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
index 96ae07035ed2..b5f3269903b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
@@ -49,6 +49,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.view.isVisible
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
@@ -64,6 +65,7 @@ import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.row.icon.appIconProvider
@@ -105,6 +107,7 @@ class NotificationInfoTest : SysuiTestCase() {
private val onUserInteractionCallback = mock<OnUserInteractionCallback>()
private val mockINotificationManager = mock<INotificationManager>()
private val channelEditorDialogController = mock<ChannelEditorDialogController>()
+ private val packageDemotionInteractor = mock<PackageDemotionInteractor>()
private val assistantFeedbackController = mock<AssistantFeedbackController>()
@Before
@@ -863,6 +866,31 @@ class NotificationInfoTest : SysuiTestCase() {
assertThat(underTest.findViewById<View>(R.id.feedback).visibility).isEqualTo(GONE)
}
+ @Test
+ @Throws(RemoteException::class)
+ fun testDismissListenerBound() {
+ val latch = CountDownLatch(1)
+ bindNotification(onCloseClick = { _: View? -> latch.countDown() })
+
+ val dismissView = underTest.findViewById<View>(R.id.inline_dismiss)
+ assertThat(dismissView.isVisible).isTrue()
+ dismissView.performClick()
+
+ // Verify that listener was triggered.
+ assertThat(latch.count).isEqualTo(0)
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ fun testDismissHiddenWhenUndismissable() {
+
+ entry.sbn.notification.flags =
+ entry.sbn.notification.flags or android.app.Notification.FLAG_NO_DISMISS
+ bindNotification(isDismissable = false)
+ val dismissView = underTest.findViewById<View>(R.id.inline_dismiss)
+ assertThat(dismissView.isVisible).isFalse()
+ }
+
private fun bindNotification(
pm: PackageManager = this.mockPackageManager,
iNotificationManager: INotificationManager = this.mockINotificationManager,
@@ -871,6 +899,7 @@ class NotificationInfoTest : SysuiTestCase() {
onUserInteractionCallback: OnUserInteractionCallback = this.onUserInteractionCallback,
channelEditorDialogController: ChannelEditorDialogController =
this.channelEditorDialogController,
+ packageDemotionInteractor: PackageDemotionInteractor = this.packageDemotionInteractor,
pkg: String = TEST_PACKAGE_NAME,
notificationChannel: NotificationChannel = this.notificationChannel,
entry: NotificationEntry = this.entry,
@@ -880,6 +909,7 @@ class NotificationInfoTest : SysuiTestCase() {
uiEventLogger: UiEventLogger = this.uiEventLogger,
isDeviceProvisioned: Boolean = true,
isNonblockable: Boolean = false,
+ isDismissable: Boolean = true,
wasShownHighPriority: Boolean = true,
assistantFeedbackController: AssistantFeedbackController = this.assistantFeedbackController,
metricsLogger: MetricsLogger = kosmos.metricsLogger,
@@ -892,6 +922,7 @@ class NotificationInfoTest : SysuiTestCase() {
iconStyleProvider,
onUserInteractionCallback,
channelEditorDialogController,
+ packageDemotionInteractor,
pkg,
notificationChannel,
entry,
@@ -901,6 +932,7 @@ class NotificationInfoTest : SysuiTestCase() {
uiEventLogger,
isDeviceProvisioned,
isNonblockable,
+ isDismissable,
wasShownHighPriority,
assistantFeedbackController,
metricsLogger,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index ce3aee1d88d2..063a04ab9f37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -36,12 +36,11 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP
import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT
import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
-import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
@@ -204,7 +203,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
val result =
NotificationRowContentBinderImpl.InflationProgress(
packageContext = mContext,
- rowImageInflater = RowImageInflater.newInstance(null),
+ rowImageInflater = RowImageInflater.newInstance(null, reinflating = false),
remoteViews = NewRemoteViews(),
contentModel = NotificationContentModel(headsUpStatusBarModel),
promotedContent = null,
@@ -539,7 +538,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
false,
notificationInflater,
FLAG_CONTENT_VIEW_PUBLIC,
- REDACTION_TYPE_SENSITIVE_CONTENT,
+ REDACTION_TYPE_OTP,
newRow,
)
// The display name should be included, but not the content or message text
@@ -567,7 +566,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
false,
notificationInflater,
FLAG_CONTENT_VIEW_PUBLIC,
- REDACTION_TYPE_SENSITIVE_CONTENT,
+ REDACTION_TYPE_OTP,
newRow,
)
var publicView = newRow.publicLayout
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index af67a04d2f2a..2d4063b2f667 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableResources;
import android.view.View;
@@ -39,7 +38,6 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -93,7 +91,6 @@ public class NotificationSnoozeTest extends SysuiTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED)
public void closeControls_withoutSave_performsUndo() {
ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
mUnderTest.mSelectedOption = options.getFirst();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index c376fad1503f..e6b2c2541447 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -29,6 +29,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,20 +61,24 @@ import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
@@ -87,6 +92,7 @@ import com.android.systemui.statusbar.notification.promoted.FakePromotedNotifica
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -101,11 +107,6 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.wmshell.BubblesTestActivity;
-import kotlin.coroutines.CoroutineContext;
-
-import kotlinx.coroutines.flow.StateFlowKt;
-import kotlinx.coroutines.test.TestScope;
-
import org.mockito.ArgumentCaptor;
import java.util.List;
@@ -114,6 +115,10 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.flow.StateFlowKt;
+import kotlinx.coroutines.test.TestScope;
+
/**
* A helper class to create {@link ExpandableNotificationRow} (for both individual and group
* notifications).
@@ -359,6 +364,7 @@ public class NotificationTestHelper {
.setUid(UID)
.setInitialPid(2000)
.setNotification(summary)
+ .setUser(USER_HANDLE)
.setParent(GroupEntry.ROOT_ENTRY)
.build();
GroupEntryBuilder groupEntry = new GroupEntryBuilder()
@@ -734,7 +740,17 @@ public class NotificationTestHelper {
mBindPipelineEntryListener.onEntryInit(entry);
mBindPipeline.manageRow(entry, row);
+ EntryAdapter entryAdapter = new EntryAdapterFactoryImpl(
+ mock(NotificationActivityStarter.class),
+ mock(MetricsLogger.class),
+ mock(PeopleNotificationIdentifier.class),
+ mock(NotificationIconStyleProvider.class),
+ mock(VisualStabilityCoordinator.class),
+ mock(NotificationActionClickManager.class)
+ ).create(entry);
+
row.initialize(
+ spy(entryAdapter),
entry,
mock(RemoteInputViewSubcomponent.Factory.class),
APP_NAME,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
index 5638e0b434aa..209dfb2d2ed6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
@@ -48,6 +48,7 @@ import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
@@ -92,6 +93,8 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
@Mock
private ChannelEditorDialogController mChannelEditorDialogController;
@Mock
+ private PackageDemotionInteractor mPackageDemotionInteractor;
+ @Mock
private AssistantFeedbackController mAssistantFeedbackController;
@Mock
private TelecomManager mTelecomManager;
@@ -138,6 +141,7 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
mMockIconStyleProvider,
mOnUserInteractionCallback,
mChannelEditorDialogController,
+ mPackageDemotionInteractor,
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
@@ -148,6 +152,7 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
true,
false,
true,
+ true,
mAssistantFeedbackController,
mMetricsLogger,
null);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
index 531b30b9547a..0fb0548582ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -18,27 +18,27 @@ package com.android.systemui.statusbar.notification.shared
import com.google.common.truth.Correspondence
val byKey: Correspondence<ActiveNotificationModel, String> =
- Correspondence.transforming({ it?.key }, "has a key of")
+ Correspondence.transforming({ it.key }, "has a key of")
val byIsAmbient: Correspondence<ActiveNotificationModel, Boolean> =
- Correspondence.transforming({ it?.isAmbient }, "has an isAmbient value of")
+ Correspondence.transforming({ it.isAmbient }, "has an isAmbient value of")
val byIsSuppressedFromStatusBar: Correspondence<ActiveNotificationModel, Boolean> =
Correspondence.transforming(
- { it?.isSuppressedFromStatusBar },
+ { it.isSuppressedFromStatusBar },
"has an isSuppressedFromStatusBar value of",
)
val byIsSilent: Correspondence<ActiveNotificationModel, Boolean> =
- Correspondence.transforming({ it?.isSilent }, "has an isSilent value of")
+ Correspondence.transforming({ it.isSilent }, "has an isSilent value of")
val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> =
- Correspondence.transforming({ it?.isRowDismissed }, "has an isRowDismissed value of")
+ Correspondence.transforming({ it.isRowDismissed }, "has an isRowDismissed value of")
val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> =
Correspondence.transforming(
- { it?.isLastMessageFromReply },
+ { it.isLastMessageFromReply },
"has an isLastMessageFromReply value of",
)
val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
- Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
+ Correspondence.transforming({ it.isPulsing }, "has an isPulsing value of")
val byIsPromoted: Correspondence<ActiveNotificationModel, Boolean> =
Correspondence.transforming(
- { it?.promotedContent != null },
+ { it.promotedContent != null },
"has (or doesn't have) a promoted content model",
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 14bbd38ece2c..761ed6186afc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -27,6 +27,7 @@ import android.view.NotificationHeaderView;
import android.view.View;
import android.widget.RemoteViews;
+import androidx.compose.ui.platform.ComposeView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -35,7 +36,9 @@ import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import org.junit.Assert;
import org.junit.Before;
@@ -273,6 +276,22 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
Assert.assertEquals(1f, header.getTopRoundness(), 0.001f);
}
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void initBundleHeader_composeview_is_initialized_once() {
+ View currentView = mChildrenContainer.getChildAt(mChildrenContainer.getChildCount() - 1);
+ Assert.assertFalse(currentView instanceof ComposeView);
+
+ BundleHeaderViewModelImpl viewModel = new BundleHeaderViewModelImpl();
+ mChildrenContainer.initBundleHeader(viewModel);
+ currentView = mChildrenContainer.getChildAt(mChildrenContainer.getChildCount() - 1);
+ Assert.assertTrue(currentView instanceof ComposeView);
+
+ mChildrenContainer.initBundleHeader(viewModel);
+ View finalView = mChildrenContainer.getChildAt(mChildrenContainer.getChildCount() - 1);
+ Assert.assertEquals(currentView, finalView);
+ }
+
private NotificationHeaderView createHeaderView(boolean lowPriority) {
Notification notification = mNotificationTestHelper.createNotification();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 954515015fd9..41cca19346f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
import android.content.pm.PackageManager
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -19,6 +20,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.RoundableState
import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,8 +34,11 @@ import com.android.systemui.statusbar.notification.headsup.NotificationsHunShare
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import kotlin.math.roundToInt
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -53,6 +58,8 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmField @Rule var expect: Expect = Expect.create()
+ private val kosmos = testKosmos()
+
private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
private val avalancheController = mock<AvalancheController>()
@@ -131,13 +138,14 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
hostView.addView(notificationRow)
if (NotificationsHunSharedAnimationValues.isEnabled) {
- headsUpAnimator = HeadsUpAnimator(context)
+ headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
}
- stackScrollAlgorithm = StackScrollAlgorithm(
- context,
- hostView,
- if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
- )
+ stackScrollAlgorithm =
+ StackScrollAlgorithm(
+ context,
+ hostView,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
private fun isTv(): Boolean {
@@ -450,6 +458,46 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_noStatusBarChip_hunTranslatedToTopOfScreen() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ headsUpAnimator?.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+
+ val statusBarHeight = 432
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ whenever(notificationRow.hasStatusBarChipDuringHeadsUpAnimation()).thenReturn(false)
+
+ resetViewStates_hunYTranslationIs(
+ expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_withStatusBarChip_hunTranslatedToBottomOfStatusBar() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ headsUpAnimator?.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+
+ val statusBarHeight = 432
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ whenever(notificationRow.hasStatusBarChipDuringHeadsUpAnimation()).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(expected = statusBarHeight - topMargin)
+ }
+
+ @Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -1525,7 +1573,11 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
fullStackHeight: Float = 3000f,
) {
ambientState.headsUpTop = headsUpTop
- ambientState.headsUpBottom = headsUpBottom
+ if (NotificationsHunSharedAnimationValues.isEnabled) {
+ headsUpAnimator.headsUpAppearHeightBottom = headsUpBottom.roundToInt()
+ } else {
+ ambientState.headsUpBottom = headsUpBottom
+ }
ambientState.stackTop = stackTop
ambientState.stackCutoff = stackCutoff
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index cb4642cc21be..8e3bdc48398f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -26,12 +26,15 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator
import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -46,7 +49,6 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.description
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
-import org.mockito.kotlin.doNothing
private const val VIEW_HEIGHT = 100
private const val FULL_SHADE_APPEAR_TRANSLATION = 300
@@ -60,6 +62,8 @@ class StackStateAnimatorTest : SysuiTestCase() {
@get:Rule val setFlagsRule = SetFlagsRule()
@get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
private lateinit var stackStateAnimator: StackStateAnimator
private lateinit var headsUpAnimator: HeadsUpAnimator
private val stackScroller: NotificationStackScrollLayout = mock()
@@ -80,13 +84,14 @@ class StackStateAnimatorTest : SysuiTestCase() {
whenever(view.viewState).thenReturn(viewState)
if (NotificationsHunSharedAnimationValues.isEnabled) {
- headsUpAnimator = HeadsUpAnimator(context)
+ headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
}
- stackStateAnimator = StackStateAnimator(
- mContext,
- stackScroller,
- if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
- )
+ stackStateAnimator =
+ StackStateAnimator(
+ mContext,
+ stackScroller,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
@Test
@@ -134,6 +139,62 @@ class StackStateAnimatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_andHasStatusBarChipFalse() {
+ val statusBarHeight = 156
+ val topMargin = 50f
+ val expectedStartY = -topMargin - HEADS_UP_ABOVE_SCREEN
+
+ headsUpAnimator.stackTopMargin = topMargin.toInt()
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator.updateResources(context)
+
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ event.headsUpHasStatusBarChip = false
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate from the top")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* isHeadsUpCycling= */ false,
+ /* onEndRunnable= */ null,
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_andHasStatusBarChipTrue() {
+ val statusBarHeight = 156
+ val topMargin = 50f
+ val expectedStartY = statusBarHeight - topMargin
+
+ headsUpAnimator!!.stackTopMargin = topMargin.toInt()
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ event.headsUpHasStatusBarChip = true
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate below status bar")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* isHeadsUpCycling= */ false,
+ /* onEndRunnable= */ null,
+ )
+ }
+
+ @Test
@DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim_flagOff() {
val screenHeight = 2000f
@@ -158,7 +219,6 @@ class StackStateAnimatorTest : SysuiTestCase() {
)
}
- @DisableFlags(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT)
@Test
@EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim_flagOn() {
@@ -185,7 +245,7 @@ class StackStateAnimatorTest : SysuiTestCase() {
}
@Test
- @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT)
fun startAnimationForEvents_startsHeadsUpDisappearAnim_flagOff() {
val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
@@ -216,6 +276,7 @@ class StackStateAnimatorTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ @DisableFlags(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT)
fun startAnimationForEvents_startsHeadsUpDisappearAnim_flagOn() {
val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 14e7cdc50227..3b836b774788 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -32,7 +32,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-@RunWithLooper
+@EnableSceneContainer
class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val underTest by lazy { kosmos.notificationsPlaceholderViewModel }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e2330f448a1b..1ea41de63e64 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -61,8 +61,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 31f8590c0378..46430afecbb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -70,6 +70,7 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -83,6 +84,7 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -155,7 +157,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
testScope = kosmos.testScope
shadeViewStateProvider = TestShadeViewStateProvider()
- Mockito.`when`(
+ whenever(
kosmos.mockStatusBarContentInsetsProvider
.getStatusBarContentInsetsForCurrentRotation()
)
@@ -163,9 +165,9 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
- Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ whenever(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(iconManager)
- Mockito.`when`(statusBarContentInsetsProviderStore.forDisplay(context.displayId))
+ whenever(statusBarContentInsetsProviderStore.forDisplay(context.displayId))
.thenReturn(kosmos.mockStatusBarContentInsetsProvider)
allowTestableLooperAsMainThread()
looper.runWithLooper {
@@ -174,7 +176,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null)
as KeyguardStatusBarView
)
- Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display)
+ whenever(keyguardStatusBarView.display).thenReturn(mContext.display)
}
controller = createController()
@@ -404,14 +406,14 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
fun updateViewState_alphaAndVisibilityGiven_viewUpdated() {
// Verify the initial values so we know the method triggers changes.
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
val newAlpha = 0.5f
val newVisibility = View.INVISIBLE
controller.updateViewState(newAlpha, newVisibility)
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
}
@Test
@@ -423,7 +425,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState(1f, View.VISIBLE)
// Since we're disabled, we stay invisible
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -444,15 +446,15 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
controller.onViewAttached()
updateStateToKeyguard()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
- Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
- Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
onFinishedGoingToSleep()
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -461,13 +463,13 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.onViewAttached()
updateStateToKeyguard()
- Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
- Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false)
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
onFinishedGoingToSleep()
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -476,13 +478,13 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.onViewAttached()
updateStateToKeyguard()
- Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
- Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
onFinishedGoingToSleep()
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -495,7 +497,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -508,7 +510,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -520,7 +522,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -532,7 +534,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -544,7 +546,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -556,7 +558,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -568,7 +570,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.setDozing(true)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -580,7 +582,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.setDozing(false)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -595,7 +597,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
}
@@ -611,7 +613,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.updateViewState(0.789f, View.VISIBLE)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
}
@@ -635,13 +637,13 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.init()
controller.onViewAttached()
updateStateToKeyguard()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
controller.setDozing(true)
// setDozing(true) should typically cause the view to hide. But since the flag is on, we
// should ignore these set dozing calls and stay the same visibility.
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -679,7 +681,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
controller.updateForHeadsUp(/* animate= */ false)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -695,7 +697,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
shadeViewStateProvider.setShouldHeadsUpBeVisible(false)
controller.updateForHeadsUp(/* animate= */ false)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -728,7 +730,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
// GIVEN the setting is off
- Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
+ whenever(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
.thenReturn(0)
// WHEN CollapsedStatusBarFragment builds the blocklist
@@ -744,7 +746,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
// GIVEN the setting is ON
- Mockito.`when`(
+ whenever(
secureSettings.getIntForUser(
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
0,
@@ -779,42 +781,52 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
controller.onViewAttached()
updateStateToKeyguard()
setDisableSystemInfo(true)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
controller.animateKeyguardStatusBarIn()
// Since we're disabled, we don't actually animate in and stay invisible
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
fun animateToGlanceableHub_affectsAlpha() =
testScope.runTest {
- controller.init()
- val transitionAlphaAmount = .5f
- ViewUtils.attachView(keyguardStatusBarView)
- looper.processAllMessages()
- updateStateToKeyguard()
- kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
- runCurrent()
- controller.updateCommunalAlphaTransition(transitionAlphaAmount)
- Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+ try {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+ } finally {
+ ViewUtils.detachView(keyguardStatusBarView)
+ }
}
@Test
fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
testScope.runTest {
- controller.init()
- val transitionAlphaAmount = .5f
- ViewUtils.attachView(keyguardStatusBarView)
- looper.processAllMessages()
- updateStateToKeyguard()
- kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
- runCurrent()
- controller.updateCommunalAlphaTransition(transitionAlphaAmount)
- kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
- runCurrent()
- Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+ try {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+ runCurrent()
+ assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+ } finally {
+ ViewUtils.detachView(keyguardStatusBarView)
+ }
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
index d16372611e88..b9d9a53fd319 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
@@ -62,11 +62,11 @@ class MultiDisplayAutoHideControllerStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
index 47967b3f0828..670c195ee5a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.lockscreenShadeTransitionController
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.domain.interactor.notificationAlertsInteractor
@@ -54,6 +55,7 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.entryAdapterFactory
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.visualInterruptionDecisionProvider
@@ -105,10 +107,14 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
private val notificationAlertsInteractor = kosmos.notificationAlertsInteractor
private val visualInterruptionDecisionProvider = kosmos.visualInterruptionDecisionProvider
+ private lateinit var factory: EntryAdapterFactoryImpl
+
private lateinit var underTest: StatusBarNotificationPresenter
@Before
fun setup() {
+ factory = kosmos.entryAdapterFactory
+
underTest = createPresenter()
if (VisualInterruptionRefactor.isEnabled) {
verifyAndCaptureSuppressors()
@@ -451,7 +457,7 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
private fun createRow(entry: NotificationEntry): ExpandableNotificationRow {
val row: ExpandableNotificationRow = mock()
if (NotificationBundleUi.isEnabled) {
- whenever(row.entryAdapter).thenReturn(entry.entryAdapter)
+ whenever(row.entryAdapter).thenReturn(factory.create(entry))
} else {
whenever(row.entry).thenReturn(entry)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 345ddae42798..c23e0e733b41 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -46,7 +46,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry.NotifEntryAdapter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentView;
@@ -135,7 +135,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
final NotificationContentView privateLayout = mock(NotificationContentView.class);
final NotificationEntry enrEntry = mock(NotificationEntry.class);
- final NotifEntryAdapter enrEntryAdapter = mock(NotifEntryAdapter.class);
+ final NotificationEntryAdapter enrEntryAdapter = mock(NotificationEntryAdapter.class);
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
@@ -178,7 +178,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
// THEN
verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
verify(enr).setUserExpanded(true);
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
}
@@ -203,7 +203,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
// THEN
verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
verify(enr).setUserExpanded(true);
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
}
@@ -217,7 +217,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
final ExpandableNotificationRow enr = mock(ExpandableNotificationRow.class);
final NotificationContentView privateLayout = mock(NotificationContentView.class);
final NotificationEntry enrEntry = mock(NotificationEntry.class);
- final NotifEntryAdapter enrEntryAdapter = mock(NotifEntryAdapter.class);
+ final NotificationEntryAdapter enrEntryAdapter = mock(NotificationEntryAdapter.class);
when(enr.getPrivateLayout()).thenReturn(privateLayout);
when(enr.getEntry()).thenReturn(enrEntry);
@@ -260,7 +260,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
// THEN
verify(mGroupExpansionManager, never()).toggleGroupExpansion(enrEntry);
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
verify(enr, never()).setUserExpanded(anyBoolean());
verify(privateLayout, never()).setOnExpandedVisibleListener(any());
}
@@ -290,7 +290,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
}
@Test
@@ -318,7 +318,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
}
@Test
@@ -346,7 +346,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(privateLayout).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
}
@Test
@@ -374,6 +374,6 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
verify(privateLayout, never()).setOnExpandedVisibleListener(onExpandedVisibleRunner);
verify(enr, never()).setUserExpanded(anyBoolean());
verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntry.class));
- verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotifEntryAdapter.class));
+ verify(mGroupExpansionManager, never()).toggleGroupExpansion(any(NotificationEntryAdapter.class));
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 58856d970711..ffde34efa7ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -48,7 +48,6 @@ import com.android.systemui.animation.back.BackAnimationSpec;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.settings.FakeDisplayTracker;
import org.junit.Before;
import org.junit.Rule;
@@ -84,8 +83,7 @@ public class SystemUIDialogTest extends SysuiTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
KosmosJavaAdapter kosmos = new KosmosJavaAdapter(this);
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mSysUiState = new SysUiState(displayTracker, kosmos.getSceneContainerPlugin());
+ mSysUiState = kosmos.getSysuiState();
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
when(mDelegate.getBackAnimationSpec(ArgumentMatchers.any()))
.thenReturn(mock(BackAnimationSpec.class));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
index bab349aa7a74..f4204af7829b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -82,6 +82,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
statusBarChipIconView = testIconView,
contentIntent = testIntent,
promotedContent = testPromotedContent,
+ isAppVisible = false,
)
// Verify model is InCall and has the correct icon, intent, and promoted content.
@@ -92,12 +93,12 @@ class OngoingCallInteractorTest : SysuiTestCase() {
assertThat(model.intent).isSameInstanceAs(testIntent)
assertThat(model.notificationKey).isEqualTo(key)
assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent)
+ assertThat(model.isAppVisible).isFalse()
}
@Test
fun ongoingCallNotification_setsAllFields_withAppVisible() =
kosmos.runTest {
- kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true
val latest by collectLastValue(underTest.ongoingCallState)
// Set up notification with icon view and intent
@@ -112,17 +113,19 @@ class OngoingCallInteractorTest : SysuiTestCase() {
statusBarChipIconView = testIconView,
contentIntent = testIntent,
promotedContent = testPromotedContent,
+ isAppVisible = true,
)
- // Verify model is InCallWithVisibleApp and has the correct icon, intent, and promoted
- // content.
- assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
- val model = latest as OngoingCallModel.InCallWithVisibleApp
+ // Verify model is InCall with visible app and has the correct icon, intent, and
+ // promoted content.
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ val model = latest as OngoingCallModel.InCall
assertThat(model.startTimeMs).isEqualTo(startTimeMs)
assertThat(model.notificationIconView).isSameInstanceAs(testIconView)
assertThat(model.intent).isSameInstanceAs(testIntent)
assertThat(model.notificationKey).isEqualTo(key)
assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent)
+ assertThat(model.isAppVisible).isTrue()
}
@Test
@@ -139,23 +142,23 @@ class OngoingCallInteractorTest : SysuiTestCase() {
@Test
fun ongoingCallNotification_appVisibleInitially_emitsInCallWithVisibleApp() =
kosmos.runTest {
- kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true
val latest by collectLastValue(underTest.ongoingCallState)
- addOngoingCallState(uid = UID)
+ addOngoingCallState(uid = UID, isAppVisible = true)
- assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue()
}
@Test
fun ongoingCallNotification_appNotVisibleInitially_emitsInCall() =
kosmos.runTest {
- kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
val latest by collectLastValue(underTest.ongoingCallState)
- addOngoingCallState(uid = UID)
+ addOngoingCallState(uid = UID, isAppVisible = false)
assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
}
@Test
@@ -164,17 +167,19 @@ class OngoingCallInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.ongoingCallState)
// Start with notification and app not visible
- kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
- addOngoingCallState(uid = UID)
+ addOngoingCallState(uid = UID, isAppVisible = false)
assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
// App becomes visible
kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)
- assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue()
// App becomes invisible again
kosmos.activityManagerRepository.fake.setIsAppVisible(UID, false)
assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
}
@Test
@@ -238,18 +243,17 @@ class OngoingCallInteractorTest : SysuiTestCase() {
.ongoingProcessRequiresStatusBarVisible
)
- kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
-
- addOngoingCallState(uid = UID)
+ addOngoingCallState(uid = UID, isAppVisible = false)
assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse()
assertThat(requiresStatusBarVisibleInRepository).isTrue()
assertThat(requiresStatusBarVisibleInWindowController).isTrue()
kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)
- assertThat(ongoingCallState)
- .isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+ assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isTrue()
assertThat(requiresStatusBarVisibleInRepository).isFalse()
assertThat(requiresStatusBarVisibleInWindowController).isFalse()
}
@@ -265,6 +269,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
addOngoingCallState()
assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse()
verify(kosmos.swipeStatusBarAwayGestureHandler, never())
.addOnGestureDetectedCallback(any(), any())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt
index c89dc5722c7a..33689931e6ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,789 +26,701 @@ import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+@OptIn(ExperimentalKairosApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileIconInteractorKairosTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ featureFlagsClassic.fake.apply { setDefault(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS) }
+ }
- private lateinit var underTest: MobileIconInteractorKairos
- private val mobileMappingsProxy = FakeMobileMappingsProxy()
- private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+ private val Kosmos.tableLogBuffer by Fixture {
+ logcatTableLogBuffer(this, "MobileIconInteractorKairosTest")
+ }
- private val connectionRepository =
- FakeMobileConnectionRepository(
- SUB_1_ID,
- logcatTableLogBuffer(kosmos, "MobileIconInteractorTest"),
- )
+ private var Kosmos.overrides: MobileIconCarrierIdOverrides by Fixture {
+ MobileIconCarrierIdOverridesImpl()
+ }
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val Kosmos.defaultSubscriptionHasDataEnabled by Fixture { MutableState(kairos, true) }
- @Before
- fun setUp() {
- underTest = createInteractor()
+ private val Kosmos.alwaysShowDataRatIcon by Fixture { MutableState(kairos, false) }
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
- connectionRepository.isInService.value = true
- }
+ private val Kosmos.alwaysUseCdmaLevel by Fixture { MutableState(kairos, false) }
- @Test
- fun gsm_usesGsmLevel() =
- testScope.runTest {
- connectionRepository.isGsm.value = true
- connectionRepository.primaryLevel.value = GSM_LEVEL
- connectionRepository.cdmaLevel.value = CDMA_LEVEL
+ private val Kosmos.isSingleCarrier by Fixture { MutableState(kairos, true) }
- var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+ private val Kosmos.mobileIsDefault by Fixture { MutableState(kairos, false) }
- assertThat(latest).isEqualTo(GSM_LEVEL)
+ private val Kosmos.defaultMobileIconMapping by Fixture {
+ MutableState(kairos, fakeMobileIconsInteractor.TEST_MAPPING)
+ }
- job.cancel()
- }
+ private val Kosmos.defaultMobileIconGroup by Fixture { MutableState(kairos, TelephonyIcons.G) }
- @Test
- fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
- testScope.runTest {
- connectionRepository.isGsm.value = true
- connectionRepository.primaryLevel.value = GSM_LEVEL
- connectionRepository.cdmaLevel.value = CDMA_LEVEL
- mobileIconsInteractor.alwaysUseCdmaLevel.value = true
+ private val Kosmos.isDefaultConnectionFailed by Fixture { MutableState(kairos, false) }
- var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+ private val Kosmos.isForceHidden by Fixture { MutableState(kairos, false) }
- assertThat(latest).isEqualTo(GSM_LEVEL)
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ MobileIconInteractorKairosImpl(
+ defaultSubscriptionHasDataEnabled,
+ alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel,
+ isSingleCarrier,
+ mobileIsDefault,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ isDefaultConnectionFailed,
+ isForceHidden,
+ connectionRepository = connectionRepo,
+ context = context,
+ carrierIdOverrides = overrides,
+ )
+ }
- job.cancel()
+ private val Kosmos.connectionRepo by Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_1_ID, kairos, tableLogBuffer).apply {
+ dataEnabled.setValue(true)
+ isInService.setValue(true)
}
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
@Test
- fun notGsm_level_default_unknown() =
- testScope.runTest {
- connectionRepository.isGsm.value = false
+ fun gsm_usesGsmLevel() = runTest {
+ connectionRepo.isGsm.setValue(true)
+ connectionRepo.primaryLevel.setValue(GSM_LEVEL)
+ connectionRepo.cdmaLevel.setValue(CDMA_LEVEL)
- var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
- job.cancel()
- }
+ assertThat(latest?.level).isEqualTo(GSM_LEVEL)
+ }
@Test
- fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
- testScope.runTest {
- connectionRepository.isGsm.value = false
- connectionRepository.primaryLevel.value = GSM_LEVEL
- connectionRepository.cdmaLevel.value = CDMA_LEVEL
- mobileIconsInteractor.alwaysUseCdmaLevel.value = true
+ fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() = runTest {
+ connectionRepo.isGsm.setValue(true)
+ connectionRepo.primaryLevel.setValue(GSM_LEVEL)
+ connectionRepo.cdmaLevel.setValue(CDMA_LEVEL)
+ // mobileIconsInteractor.alwaysUseCdmaLevel.setValue(true)
+ alwaysUseCdmaLevel.setValue(true)
- var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- assertThat(latest).isEqualTo(CDMA_LEVEL)
-
- job.cancel()
- }
+ assertThat(latest?.level).isEqualTo(GSM_LEVEL)
+ }
@Test
- fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
- testScope.runTest {
- connectionRepository.isGsm.value = false
- connectionRepository.primaryLevel.value = GSM_LEVEL
- connectionRepository.cdmaLevel.value = CDMA_LEVEL
- mobileIconsInteractor.alwaysUseCdmaLevel.value = false
-
- var latest: Int? = null
- val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+ fun notGsm_level_default_unknown() = runTest {
+ connectionRepo.isGsm.setValue(false)
- assertThat(latest).isEqualTo(GSM_LEVEL)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- job.cancel()
- }
+ assertThat(latest?.level).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ }
@Test
- fun numberOfLevels_comesFromRepo_whenApplicable() =
- testScope.runTest {
- var latest: Int? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels }
- .launchIn(this)
-
- connectionRepository.numberOfLevels.value = 5
- assertThat(latest).isEqualTo(5)
+ fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() = runTest {
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.primaryLevel.setValue(GSM_LEVEL)
+ connectionRepo.cdmaLevel.setValue(CDMA_LEVEL)
+ // mobileIconsInteractor.alwaysUseCdmaLevel.setValue(true)
+ alwaysUseCdmaLevel.setValue(true)
- connectionRepository.numberOfLevels.value = 4
- assertThat(latest).isEqualTo(4)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- job.cancel()
- }
+ assertThat(latest?.level).isEqualTo(CDMA_LEVEL)
+ }
@Test
- fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() =
- testScope.runTest {
- connectionRepository.inflateSignalStrength.value = false
- val latest by collectLastValue(underTest.signalLevelIcon)
+ fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() = runTest {
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.primaryLevel.setValue(GSM_LEVEL)
+ connectionRepo.cdmaLevel.setValue(CDMA_LEVEL)
+ // mobileIconsInteractor.alwaysUseCdmaLevel.setValue(false)
+ alwaysUseCdmaLevel.setValue(false)
- connectionRepository.primaryLevel.value = 4
- assertThat(latest!!.level).isEqualTo(4)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- connectionRepository.inflateSignalStrength.value = true
- connectionRepository.primaryLevel.value = 4
-
- // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level
- assertThat(latest!!.level).isEqualTo(5)
- }
+ assertThat(latest?.level).isEqualTo(GSM_LEVEL)
+ }
@Test
- fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() =
- testScope.runTest {
- connectionRepository.allowNetworkSliceIndicator.value = true
- val latest by collectLastValue(underTest.showSliceAttribution)
+ fun numberOfLevels_comesFromRepo_whenApplicable() = runTest {
+ val latest by
+ underTest.signalLevelIcon
+ .map { (it as? SignalIconModel.Cellular)?.numberOfLevels }
+ .collectLastValue()
- connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+ connectionRepo.numberOfLevels.setValue(5)
+ assertThat(latest).isEqualTo(5)
- assertThat(latest).isTrue()
- }
+ connectionRepo.numberOfLevels.setValue(4)
+ assertThat(latest).isEqualTo(4)
+ }
@Test
- fun networkSlice_configOn_noPrioritizedCaps_noSlice() =
- testScope.runTest {
- connectionRepository.allowNetworkSliceIndicator.value = true
- val latest by collectLastValue(underTest.showSliceAttribution)
+ fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() = runTest {
+ connectionRepo.inflateSignalStrength.setValue(false)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+ connectionRepo.primaryLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
- assertThat(latest).isFalse()
- }
+ connectionRepo.inflateSignalStrength.setValue(true)
+ connectionRepo.primaryLevel.setValue(4)
+
+ // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level
+ assertThat(latest!!.level).isEqualTo(5)
+ }
@Test
- fun networkSlice_configOff_hasPrioritizedCaps_noSlice() =
- testScope.runTest {
- connectionRepository.allowNetworkSliceIndicator.value = false
- val latest by collectLastValue(underTest.showSliceAttribution)
+ fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(true)
+ val latest by underTest.showSliceAttribution.collectLastValue()
- connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(true)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun networkSlice_configOff_noPrioritizedCaps_noSlice() =
- testScope.runTest {
- connectionRepository.allowNetworkSliceIndicator.value = false
- val latest by collectLastValue(underTest.showSliceAttribution)
+ fun networkSlice_configOn_noPrioritizedCaps_noSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(true)
+ val latest by underTest.showSliceAttribution.collectLastValue()
- connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(false)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun iconGroup_three_g() =
- testScope.runTest {
- connectionRepository.resolvedNetworkType.value =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
-
- var latest: NetworkTypeIconModel? = null
- val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+ fun networkSlice_configOff_hasPrioritizedCaps_noSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(false)
+ val latest by underTest.showSliceAttribution.collectLastValue()
- assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G))
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(true)
- job.cancel()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun iconGroup_updates_on_change() =
- testScope.runTest {
- connectionRepository.resolvedNetworkType.value =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ fun networkSlice_configOff_noPrioritizedCaps_noSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(false)
+ val latest by underTest.showSliceAttribution.collectLastValue()
- var latest: NetworkTypeIconModel? = null
- val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(false)
- connectionRepository.resolvedNetworkType.value =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
-
- assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G))
-
- job.cancel()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun iconGroup_5g_override_type() =
- testScope.runTest {
- connectionRepository.resolvedNetworkType.value =
- OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
-
- var latest: NetworkTypeIconModel? = null
- val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+ fun iconGroup_three_g() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ )
- assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G))
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
- job.cancel()
- }
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G))
+ }
@Test
- fun iconGroup_default_if_no_lookup() =
- testScope.runTest {
- connectionRepository.resolvedNetworkType.value =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
+ fun iconGroup_updates_on_change() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ )
- var latest: NetworkTypeIconModel? = null
- val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
- assertThat(latest)
- .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON))
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
+ )
- job.cancel()
- }
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G))
+ }
@Test
- fun iconGroup_carrierMerged_usesOverride() =
- testScope.runTest {
- connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
-
- var latest: NetworkTypeIconModel? = null
- val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+ fun iconGroup_5g_override_type() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
+ )
- assertThat(latest)
- .isEqualTo(
- NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride)
- )
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
- job.cancel()
- }
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G))
+ }
@Test
- fun overrideIcon_usesCarrierIdOverride() =
- testScope.runTest {
- val overrides =
- mock<MobileIconCarrierIdOverrides>().also {
- whenever(it.carrierIdEntryExists(anyInt())).thenReturn(true)
- whenever(it.getOverrideFor(anyInt(), anyString(), any())).thenReturn(1234)
- }
+ fun iconGroup_default_if_no_lookup() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
+ )
- underTest = createInteractor(overrides)
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
- connectionRepository.resolvedNetworkType.value =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON))
+ }
- var latest: NetworkTypeIconModel? = null
- val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+ @Test
+ fun iconGroup_carrierMerged_usesOverride() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(CarrierMergedNetworkType)
- assertThat(latest)
- .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234))
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
- job.cancel()
- }
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride))
+ }
@Test
- fun alwaysShowDataRatIcon_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+ fun overrideIcon_usesCarrierIdOverride() = runTest {
+ overrides =
+ mock<MobileIconCarrierIdOverrides> {
+ on { carrierIdEntryExists(anyInt()) } doReturn true
+ on { getOverrideFor(anyInt(), anyString(), any()) } doReturn 1234
+ }
- mobileIconsInteractor.alwaysShowDataRatIcon.value = true
- assertThat(latest).isTrue()
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ )
- mobileIconsInteractor.alwaysShowDataRatIcon.value = false
- assertThat(latest).isFalse()
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
- job.cancel()
- }
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234))
+ }
@Test
- fun dataState_connected() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+ fun alwaysShowDataRatIcon_matchesParent() = runTest {
+ val latest by underTest.alwaysShowDataRatIcon.collectLastValue()
- connectionRepository.dataConnectionState.value = DataConnectionState.Connected
+ // mobileIconsInteractor.alwaysShowDataRatIcon.setValue(true)
+ alwaysShowDataRatIcon.setValue(true)
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ // mobileIconsInteractor.alwaysShowDataRatIcon.setValue(false)
+ alwaysShowDataRatIcon.setValue(false)
- @Test
- fun dataState_notConnected() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ }
- connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
+ @Test
+ fun dataState_connected() = runTest {
+ val latest by underTest.isDataConnected.collectLastValue()
- assertThat(latest).isFalse()
+ connectionRepo.dataConnectionState.setValue(DataConnectionState.Connected)
- job.cancel()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun isInService_usesRepositoryValue() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isInService.onEach { latest = it }.launchIn(this)
+ fun dataState_notConnected() = runTest {
+ val latest by underTest.isDataConnected.collectLastValue()
- connectionRepository.isInService.value = true
+ connectionRepo.dataConnectionState.setValue(DataConnectionState.Disconnected)
- assertThat(latest).isTrue()
+ assertThat(latest).isFalse()
+ }
- connectionRepository.isInService.value = false
+ @Test
+ fun isInService_usesRepositoryValue() = runTest {
+ val latest by underTest.isInService.collectLastValue()
- assertThat(latest).isFalse()
+ connectionRepo.isInService.setValue(true)
- job.cancel()
- }
+ assertThat(latest).isTrue()
- @Test
- fun roaming_isGsm_usesConnectionModel() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+ connectionRepo.isInService.setValue(false)
- connectionRepository.cdmaRoaming.value = true
- connectionRepository.isGsm.value = true
- connectionRepository.isRoaming.value = false
+ assertThat(latest).isFalse()
+ }
- assertThat(latest).isFalse()
+ @Test
+ fun roaming_isGsm_usesConnectionModel() = runTest {
+ val latest by underTest.isRoaming.collectLastValue()
- connectionRepository.isRoaming.value = true
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(true)
+ connectionRepo.isRoaming.setValue(false)
- assertThat(latest).isTrue()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ connectionRepo.isRoaming.setValue(true)
- @Test
- fun roaming_isCdma_usesCdmaRoamingBit() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ }
- connectionRepository.cdmaRoaming.value = false
- connectionRepository.isGsm.value = false
- connectionRepository.isRoaming.value = true
+ @Test
+ fun roaming_isCdma_usesCdmaRoamingBit() = runTest {
+ val latest by underTest.isRoaming.collectLastValue()
- assertThat(latest).isFalse()
+ connectionRepo.cdmaRoaming.setValue(false)
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.isRoaming.setValue(true)
- connectionRepository.cdmaRoaming.value = true
- connectionRepository.isGsm.value = false
- connectionRepository.isRoaming.value = false
+ assertThat(latest).isFalse()
- assertThat(latest).isTrue()
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.isRoaming.setValue(false)
- job.cancel()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun roaming_falseWhileCarrierNetworkChangeActive() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+ fun roaming_falseWhileCarrierNetworkChangeActive() = runTest {
+ val latest by underTest.isRoaming.collectLastValue()
- connectionRepository.cdmaRoaming.value = true
- connectionRepository.isGsm.value = false
- connectionRepository.isRoaming.value = true
- connectionRepository.carrierNetworkChangeActive.value = true
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.isRoaming.setValue(true)
+ connectionRepo.carrierNetworkChangeActive.setValue(true)
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- connectionRepository.cdmaRoaming.value = true
- connectionRepository.isGsm.value = true
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(true)
- assertThat(latest).isFalse()
-
- job.cancel()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() =
- testScope.runTest {
- var latest: NetworkNameModel? = null
- val job = underTest.networkName.onEach { latest = it }.launchIn(this)
-
- val testOperatorName = "operatorAlphaShort"
+ fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = runTest {
+ val latest by underTest.networkName.collectLastValue()
- // Default network name, operator name is non-null, uses the operator name
- connectionRepository.networkName.value = DEFAULT_NAME_MODEL
- connectionRepository.operatorAlphaShort.value = testOperatorName
+ val testOperatorName = "operatorAlphaShort"
- assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepo.networkName.setValue(DEFAULT_NAME_MODEL)
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
- // Default network name, operator name is null, uses the default
- connectionRepository.operatorAlphaShort.value = null
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
- assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ // Default network name, operator name is null, uses the default
+ connectionRepo.operatorAlphaShort.setValue(null)
- // Derived network name, operator name non-null, uses the derived name
- connectionRepository.networkName.value = DERIVED_NAME_MODEL
- connectionRepository.operatorAlphaShort.value = testOperatorName
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
- assertThat(latest).isEqualTo(DERIVED_NAME_MODEL)
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepo.networkName.setValue(DERIVED_NAME_MODEL)
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
- job.cancel()
- }
+ assertThat(latest).isEqualTo(DERIVED_NAME_MODEL)
+ }
@Test
- fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() =
- testScope.runTest {
- var latest: String? = null
- val job = underTest.carrierName.onEach { latest = it }.launchIn(this)
-
- val testOperatorName = "operatorAlphaShort"
+ fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = runTest {
+ val latest by underTest.carrierName.collectLastValue()
- // Default network name, operator name is non-null, uses the operator name
- connectionRepository.carrierName.value = DEFAULT_NAME_MODEL
- connectionRepository.operatorAlphaShort.value = testOperatorName
+ val testOperatorName = "operatorAlphaShort"
- assertThat(latest).isEqualTo(testOperatorName)
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepo.carrierName.setValue(DEFAULT_NAME_MODEL)
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
- // Default network name, operator name is null, uses the default
- connectionRepository.operatorAlphaShort.value = null
+ assertThat(latest).isEqualTo(testOperatorName)
- assertThat(latest).isEqualTo(DEFAULT_NAME)
+ // Default network name, operator name is null, uses the default
+ connectionRepo.operatorAlphaShort.setValue(null)
- // Derived network name, operator name non-null, uses the derived name
- connectionRepository.carrierName.value =
- NetworkNameModel.SubscriptionDerived(DERIVED_NAME)
- connectionRepository.operatorAlphaShort.value = testOperatorName
+ assertThat(latest).isEqualTo(DEFAULT_NAME)
- assertThat(latest).isEqualTo(DERIVED_NAME)
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepo.carrierName.setValue(NetworkNameModel.SubscriptionDerived(DERIVED_NAME))
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
- job.cancel()
- }
+ assertThat(latest).isEqualTo(DERIVED_NAME)
+ }
@Test
- fun isSingleCarrier_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ fun isSingleCarrier_matchesParent() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
- mobileIconsInteractor.isSingleCarrier.value = true
- assertThat(latest).isTrue()
+ // mobileIconsInteractor.isSingleCarrier.setValue(true)
+ isSingleCarrier.setValue(true)
+ assertThat(latest).isTrue()
- mobileIconsInteractor.isSingleCarrier.value = false
- assertThat(latest).isFalse()
-
- job.cancel()
- }
+ // mobileIconsInteractor.isSingleCarrier.setValue(false)
+ isSingleCarrier.setValue(false)
+ assertThat(latest).isFalse()
+ }
@Test
- fun isForceHidden_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
-
- mobileIconsInteractor.isForceHidden.value = true
- assertThat(latest).isTrue()
+ fun isForceHidden_matchesParent() = runTest {
+ val latest by underTest.isForceHidden.collectLastValue()
- mobileIconsInteractor.isForceHidden.value = false
- assertThat(latest).isFalse()
+ // mobileIconsInteractor.isForceHidden.setValue(true)
+ isForceHidden.setValue(true)
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ // mobileIconsInteractor.isForceHidden.setValue(false)
+ isForceHidden.setValue(false)
+ assertThat(latest).isFalse()
+ }
@Test
- fun isAllowedDuringAirplaneMode_matchesRepo() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+ fun isAllowedDuringAirplaneMode_matchesRepo() = runTest {
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
- connectionRepository.isAllowedDuringAirplaneMode.value = true
- assertThat(latest).isTrue()
+ connectionRepo.isAllowedDuringAirplaneMode.setValue(true)
+ assertThat(latest).isTrue()
- connectionRepository.isAllowedDuringAirplaneMode.value = false
- assertThat(latest).isFalse()
- }
+ connectionRepo.isAllowedDuringAirplaneMode.setValue(false)
+ assertThat(latest).isFalse()
+ }
@Test
- fun cellBasedIconId_correctLevel_notCutout() =
- testScope.runTest {
- connectionRepository.isNonTerrestrial.value = false
- connectionRepository.isInService.value = true
- connectionRepository.primaryLevel.value = 1
- connectionRepository.setDataEnabled(false)
- connectionRepository.isNonTerrestrial.value = false
+ fun cellBasedIconId_correctLevel_notCutout() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
+ connectionRepo.primaryLevel.setValue(1)
+ connectionRepo.dataEnabled.setValue(true)
+ connectionRepo.isNonTerrestrial.setValue(false)
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular }
- .launchIn(this)
+ val latest by
+ underTest.signalLevelIcon.map { it as? SignalIconModel.Cellular }.collectLastValue()
- assertThat(latest?.level).isEqualTo(1)
- assertThat(latest?.showExclamationMark).isFalse()
+ assertThat(latest?.level).isEqualTo(1)
- job.cancel()
- }
+ // TODO: need to provision MobileIconsInteractorKairos#isDefaultConnectionFailed +
+ // defaultSubscriptionHasDataEnabled?
+ assertThat(latest?.showExclamationMark).isEqualTo(false)
+ }
@Test
- fun icon_usesLevelFromInteractor() =
- testScope.runTest {
- connectionRepository.isNonTerrestrial.value = false
- connectionRepository.isInService.value = true
+ fun icon_usesLevelFromInteractor() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
- var latest: SignalIconModel? = null
- val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- connectionRepository.primaryLevel.value = 3
- assertThat(latest!!.level).isEqualTo(3)
+ connectionRepo.primaryLevel.setValue(3)
+ assertThat(latest!!.level).isEqualTo(3)
- connectionRepository.primaryLevel.value = 1
- assertThat(latest!!.level).isEqualTo(1)
-
- job.cancel()
- }
+ connectionRepo.primaryLevel.setValue(1)
+ assertThat(latest!!.level).isEqualTo(1)
+ }
@Test
- fun cellBasedIcon_usesNumberOfLevelsFromInteractor() =
- testScope.runTest {
- connectionRepository.isNonTerrestrial.value = false
-
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular }
- .launchIn(this)
+ fun cellBasedIcon_usesNumberOfLevelsFromInteractor() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
- connectionRepository.numberOfLevels.value = 5
- assertThat(latest!!.numberOfLevels).isEqualTo(5)
+ val latest by
+ underTest.signalLevelIcon.map { it as? SignalIconModel.Cellular }.collectLastValue()
- connectionRepository.numberOfLevels.value = 2
- assertThat(latest!!.numberOfLevels).isEqualTo(2)
+ connectionRepo.numberOfLevels.setValue(5)
+ assertThat(latest!!.numberOfLevels).isEqualTo(5)
- job.cancel()
- }
+ connectionRepo.numberOfLevels.setValue(2)
+ assertThat(latest!!.numberOfLevels).isEqualTo(2)
+ }
@Test
- fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() =
- testScope.runTest {
- connectionRepository.isNonTerrestrial.value = false
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
-
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular }
- .launchIn(this)
+ fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.dataEnabled.setValue(false)
+ defaultSubscriptionHasDataEnabled.setValue(false)
- assertThat(latest!!.showExclamationMark).isTrue()
+ val latest by underTest.signalLevelIcon.collectLastValue()
- job.cancel()
- }
+ assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isTrue()
+ }
@Test
- fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() =
- testScope.runTest {
- connectionRepository.isNonTerrestrial.value = false
- mobileIconsInteractor.isDefaultConnectionFailed.value = true
+ fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ // mobileIconsInteractor.isDefaultConnectionFailed.setValue(true)
+ isDefaultConnectionFailed.setValue(true)
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular }
- .launchIn(this)
+ val latest by underTest.signalLevelIcon.collectLastValue()
- assertThat(latest!!.showExclamationMark).isTrue()
-
- job.cancel()
- }
+ assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isTrue()
+ }
@Test
- fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() =
- testScope.runTest {
- connectionRepository.isNonTerrestrial.value = false
- connectionRepository.isInService.value = true
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
- mobileIconsInteractor.isDefaultConnectionFailed.value = false
-
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular }
- .launchIn(this)
+ fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
+ connectionRepo.dataEnabled.setValue(true)
+ // mobileIconsInteractor.isDefaultConnectionFailed.setValue(false)
+ isDefaultConnectionFailed.setValue(false)
- assertThat(latest!!.showExclamationMark).isFalse()
+ val latest by underTest.signalLevelIcon.collectLastValue()
- job.cancel()
- }
+ assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isFalse()
+ }
@Test
- fun cellBasedIcon_usesEmptyState_whenNotInService() =
- testScope.runTest {
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular }
- .launchIn(this)
+ fun cellBasedIcon_usesEmptyState_whenNotInService() = runTest {
+ val latest by
+ underTest.signalLevelIcon.map { it as SignalIconModel.Cellular }.collectLastValue()
- connectionRepository.isNonTerrestrial.value = false
- connectionRepository.isInService.value = false
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(false)
- assertThat(latest?.level).isEqualTo(0)
- assertThat(latest?.showExclamationMark).isTrue()
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
- // Changing the level doesn't overwrite the disabled state
- connectionRepository.primaryLevel.value = 2
- assertThat(latest?.level).isEqualTo(0)
- assertThat(latest?.showExclamationMark).isTrue()
+ // Changing the level doesn't overwrite the disabled state
+ connectionRepo.primaryLevel.setValue(2)
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
- // Once back in service, the regular icon appears
- connectionRepository.isInService.value = true
- assertThat(latest?.level).isEqualTo(2)
- assertThat(latest?.showExclamationMark).isFalse()
-
- job.cancel()
- }
+ // Once back in service, the regular icon appears
+ connectionRepo.isInService.setValue(true)
+ assertThat(latest?.level).isEqualTo(2)
+ assertThat(latest?.showExclamationMark).isFalse()
+ }
@Test
- fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
- testScope.runTest {
- var latest: SignalIconModel.Cellular? = null
- val job =
- underTest.signalLevelIcon
- .onEach { latest = it as? SignalIconModel.Cellular? }
- .launchIn(this)
-
- connectionRepository.isNonTerrestrial.value = false
- connectionRepository.isInService.value = true
- connectionRepository.carrierNetworkChangeActive.value = true
- connectionRepository.primaryLevel.value = 1
- connectionRepository.cdmaLevel.value = 1
+ fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = runTest {
+ val latest by
+ underTest.signalLevelIcon.map { it as SignalIconModel.Cellular }.collectLastValue()
- assertThat(latest!!.level).isEqualTo(1)
- assertThat(latest!!.carrierNetworkChange).isTrue()
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
+ connectionRepo.carrierNetworkChangeActive.setValue(true)
+ connectionRepo.primaryLevel.setValue(1)
+ connectionRepo.cdmaLevel.setValue(1)
- // SignalIconModel respects the current level
- connectionRepository.primaryLevel.value = 2
+ assertThat(latest!!.level).isEqualTo(1)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
- assertThat(latest!!.level).isEqualTo(2)
- assertThat(latest!!.carrierNetworkChange).isTrue()
+ // SignalIconModel respects the current level
+ connectionRepo.primaryLevel.setValue(2)
- job.cancel()
- }
+ assertThat(latest!!.level).isEqualTo(2)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+ }
@Test
- fun satBasedIcon_isUsedWhenNonTerrestrial() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
+ fun satBasedIcon_isUsedWhenNonTerrestrial() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
- // Start off using cellular
- assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+ // Start off using cellular
+ assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
- connectionRepository.isNonTerrestrial.value = true
+ connectionRepo.isNonTerrestrial.setValue(true)
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
- }
+ assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+ }
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
// See b/346904529 for more context
- fun satBasedIcon_doesNotInflateSignalStrength_flagOff() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
+ fun satBasedIcon_doesNotInflateSignalStrength_flagOff() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
- // GIVEN a satellite connection
- connectionRepository.isNonTerrestrial.value = true
- // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
- connectionRepository.inflateSignalStrength.value = true
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepo.inflateSignalStrength.setValue(true)
- connectionRepository.primaryLevel.value = 4
- assertThat(latest!!.level).isEqualTo(4)
+ connectionRepo.primaryLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
- connectionRepository.inflateSignalStrength.value = true
- connectionRepository.primaryLevel.value = 4
+ connectionRepo.inflateSignalStrength.setValue(true)
+ connectionRepo.primaryLevel.setValue(4)
- // Icon level is unaffected
- assertThat(latest!!.level).isEqualTo(4)
- }
+ // Icon level is unaffected
+ assertThat(latest!!.level).isEqualTo(4)
+ }
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
// See b/346904529 for more context
- fun satBasedIcon_doesNotInflateSignalStrength_flagOn() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
+ fun satBasedIcon_doesNotInflateSignalStrength_flagOn() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
- // GIVEN a satellite connection
- connectionRepository.isNonTerrestrial.value = true
- // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
- connectionRepository.inflateSignalStrength.value = true
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepo.inflateSignalStrength.setValue(true)
- connectionRepository.satelliteLevel.value = 4
- assertThat(latest!!.level).isEqualTo(4)
+ connectionRepo.satelliteLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
- connectionRepository.inflateSignalStrength.value = true
- connectionRepository.primaryLevel.value = 4
+ connectionRepo.inflateSignalStrength.setValue(true)
+ connectionRepo.primaryLevel.setValue(4)
- // Icon level is unaffected
- assertThat(latest!!.level).isEqualTo(4)
- }
+ // Icon level is unaffected
+ assertThat(latest!!.level).isEqualTo(4)
+ }
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun satBasedIcon_usesPrimaryLevel_flagOff() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
+ fun satBasedIcon_usesPrimaryLevel_flagOff() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
- // GIVEN a satellite connection
- connectionRepository.isNonTerrestrial.value = true
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
- // GIVEN primary level is set
- connectionRepository.primaryLevel.value = 4
- connectionRepository.satelliteLevel.value = 0
+ // GIVEN primary level is set
+ connectionRepo.primaryLevel.setValue(4)
+ connectionRepo.satelliteLevel.setValue(0)
- // THEN icon uses the primary level because the flag is off
- assertThat(latest!!.level).isEqualTo(4)
- }
+ // THEN icon uses the primary level because the flag is off
+ assertThat(latest!!.level).isEqualTo(4)
+ }
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun satBasedIcon_usesSatelliteLevel_flagOn() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
+ fun satBasedIcon_usesSatelliteLevel_flagOn() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
- // GIVEN a satellite connection
- connectionRepository.isNonTerrestrial.value = true
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
- // GIVEN satellite level is set
- connectionRepository.satelliteLevel.value = 4
- connectionRepository.primaryLevel.value = 0
+ // GIVEN satellite level is set
+ connectionRepo.satelliteLevel.setValue(4)
+ connectionRepo.primaryLevel.setValue(0)
- // THEN icon uses the satellite level because the flag is on
- assertThat(latest!!.level).isEqualTo(4)
- }
+ // THEN icon uses the satellite level because the flag is on
+ assertThat(latest!!.level).isEqualTo(4)
+ }
/**
* Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is
@@ -816,43 +728,23 @@ class MobileIconInteractorKairosTest : SysuiTestCase() {
*/
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
-
- // GIVEN a satellite connection
- connectionRepository.isNonTerrestrial.value = true
- // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
- connectionRepository.inflateSignalStrength.value = true
+ fun satBasedIcon_reportsLevelZeroWhenOutOfService() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
- connectionRepository.primaryLevel.value = 4
- assertThat(latest!!.level).isEqualTo(4)
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepo.inflateSignalStrength.setValue(true)
- connectionRepository.isInService.value = false
- connectionRepository.primaryLevel.value = 4
+ connectionRepo.primaryLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
- // THEN level reports 0, by policy
- assertThat(latest!!.level).isEqualTo(0)
- }
+ connectionRepo.isInService.setValue(false)
+ connectionRepo.primaryLevel.setValue(4)
- private fun createInteractor(
- overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
- ) =
- MobileIconInteractorKairosImpl(
- testScope.backgroundScope,
- mobileIconsInteractor.activeDataConnectionHasDataEnabled,
- mobileIconsInteractor.alwaysShowDataRatIcon,
- mobileIconsInteractor.alwaysUseCdmaLevel,
- mobileIconsInteractor.isSingleCarrier,
- mobileIconsInteractor.mobileIsDefault,
- mobileIconsInteractor.defaultMobileIconMapping,
- mobileIconsInteractor.defaultMobileIconGroup,
- mobileIconsInteractor.isDefaultConnectionFailed,
- mobileIconsInteractor.isForceHidden,
- connectionRepository,
- context,
- overrides,
- )
+ // THEN level reports 0, by policy
+ assertThat(latest!!.level).isEqualTo(0)
+ }
companion object {
private const val GSM_LEVEL = 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt
index a9360d139a3d..4f6439d2d0b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,186 +28,164 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fake
import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.runKairosTest
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.testKosmos
-import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.carrierConfigTracker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.test.advanceTimeBy
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+@OptIn(ExperimentalKairosApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileIconsInteractorKairosTest : SysuiTestCase() {
- private val kosmos by lazy {
+
+ private val kosmos =
testKosmos().apply {
- mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest"
- mobileConnectionsRepository.fake.run {
- setMobileConnectionRepositoryMap(
- mapOf(
- SUB_1_ID to FakeMobileConnectionRepository(SUB_1_ID, mock()),
- SUB_2_ID to FakeMobileConnectionRepository(SUB_2_ID, mock()),
- SUB_3_ID to FakeMobileConnectionRepository(SUB_3_ID, mock()),
- SUB_4_ID to FakeMobileConnectionRepository(SUB_4_ID, mock()),
- )
- )
- setActiveMobileDataSubscriptionId(SUB_1_ID)
- }
+ useUnconfinedTestDispatcher()
+ mobileConnectionsRepositoryKairos =
+ fakeMobileConnectionsRepositoryKairos.apply {
+ setActiveMobileDataSubscriptionId(SUB_1_ID)
+ subscriptions.setValue(listOf(SUB_1, SUB_2, SUB_3_OPP, SUB_4_OPP))
+ }
featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
}
- }
- // shortcut rename
- private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
-
- private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
-
- private val Kosmos.underTest by Fixture {
- MobileIconsInteractorKairosImpl(
- mobileConnectionsRepository,
- carrierConfigTracker,
- tableLogger = mock(),
- connectivityRepository,
- FakeUserSetupRepository(),
- testScope.backgroundScope,
- context,
- featureFlagsClassic,
- )
- }
+ private val Kosmos.underTest
+ get() = mobileIconsInteractorKairos
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
@Test
- fun filteredSubscriptions_default() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ fun filteredSubscriptions_default() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(emptyList())
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
- }
+ assertThat(latest).isEqualTo(emptyList<SubscriptionModel>())
+ }
// Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
@Test
- fun filteredSubscriptions_moreThanTwo_doesNotFilter() =
- kosmos.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ fun filteredSubscriptions_moreThanTwo_doesNotFilter() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)
+ )
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_4_ID)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
- }
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+ }
@Test
- fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
- kosmos.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
- }
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+ }
@Test
- fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() =
- kosmos.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_3_OPP, SUB_4_OPP))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
- }
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
+ }
@Test
- fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() =
- kosmos.runTest {
- val (sub1, sub2) =
- createSubscriptionPair(
- subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
- opportunistic = Pair(true, true),
- grouped = false,
- )
- connectionsRepository.setSubscriptions(listOf(sub1, sub2))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() = runTest {
+ val (sub1, sub2) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
+ opportunistic = Pair(true, true),
+ grouped = false,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_1_ID)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(sub1, sub2))
- }
+ assertThat(latest).isEqualTo(listOf(sub1, sub2))
+ }
@Test
- fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() =
- kosmos.runTest {
- val (sub3, sub4) =
- createSubscriptionPair(
- subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
- opportunistic = Pair(true, true),
- grouped = true,
- )
- connectionsRepository.setSubscriptions(listOf(sub3, sub4))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
- .thenReturn(false)
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() = runTest {
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub3, sub4))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- // Filtered subscriptions should show the active one when the config is false
- assertThat(latest).isEqualTo(listOf(sub3))
- }
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
@Test
- fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() =
- kosmos.runTest {
- val (sub3, sub4) =
- createSubscriptionPair(
- subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
- opportunistic = Pair(true, true),
- grouped = true,
- )
- connectionsRepository.setSubscriptions(listOf(sub3, sub4))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
- whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
- .thenReturn(false)
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() = runTest {
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub3, sub4))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- // Filtered subscriptions should show the active one when the config is false
- assertThat(latest).isEqualTo(listOf(sub4))
- }
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(sub4))
+ }
@Test
fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() =
- kosmos.runTest {
+ runTest {
val (sub1, sub3) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
opportunistic = Pair(false, true),
grouped = true,
)
- connectionsRepository.setSubscriptions(listOf(sub1, sub3))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_1_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
@@ -216,19 +194,19 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() {
@Test
fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
- kosmos.runTest {
+ runTest {
val (sub1, sub3) =
createSubscriptionPair(
subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
opportunistic = Pair(false, true),
grouped = true,
)
- connectionsRepository.setSubscriptions(listOf(sub1, sub3))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
@@ -236,135 +214,130 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() {
}
@Test
- fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() =
- kosmos.runTest {
- val (sub1, sub3) =
- createSubscriptionPair(
- subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
- opportunistic = Pair(true, true),
- grouped = true,
- )
- connectionsRepository.setSubscriptions(listOf(sub1, sub3))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID
- whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
- .thenReturn(false)
+ fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(sub3))
- }
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
@Test
- fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() =
- kosmos.runTest {
- val (sub1, sub3) =
- createSubscriptionPair(
- subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
- opportunistic = Pair(true, true),
- grouped = true,
- )
- connectionsRepository.setSubscriptions(listOf(sub1, sub3))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID
- whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
- .thenReturn(false)
+ fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(sub1))
- }
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
@Test
- fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() =
- kosmos.runTest {
- // GIVEN the flag is false
- featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
+ fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() = runTest {
+ // GIVEN the flag is false
+ featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
- // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING
- val sub1 =
- SubscriptionModel(
- subscriptionId = SUB_1_ID,
- isOpportunistic = false,
- carrierName = "Carrier 1",
- profileClass = PROFILE_CLASS_PROVISIONING,
- )
+ // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
- connectionsRepository.setSubscriptions(listOf(sub1))
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1))
- // WHEN filtering is applied
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ // WHEN filtering is applied
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- // THEN the provisioning sub is still present (unfiltered)
- assertThat(latest).isEqualTo(listOf(sub1))
- }
+ // THEN the provisioning sub is still present (unfiltered)
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
@Test
- fun filteredSubscriptions_filtersOutProvisioningSubs() =
- kosmos.runTest {
- val sub1 =
- SubscriptionModel(
- subscriptionId = SUB_1_ID,
- isOpportunistic = false,
- carrierName = "Carrier 1",
- profileClass = PROFILE_CLASS_UNSET,
- )
- val sub2 =
- SubscriptionModel(
- subscriptionId = SUB_2_ID,
- isOpportunistic = false,
- carrierName = "Carrier 2",
- profileClass = PROFILE_CLASS_PROVISIONING,
- )
+ fun filteredSubscriptions_filtersOutProvisioningSubs() = runTest {
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
- connectionsRepository.setSubscriptions(listOf(sub1, sub2))
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2))
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(sub1))
- }
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
/** Note: I'm not sure if this will ever be the case, but we can test it at least */
@Test
- fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() =
- kosmos.runTest {
- // This is a contrived test case, where the active subId is the one that would
- // also be filtered by opportunistic filtering.
-
- // GIVEN grouped, opportunistic subscriptions
- val groupUuid = ParcelUuid(UUID.randomUUID())
- val sub1 =
- SubscriptionModel(
- subscriptionId = 1,
- isOpportunistic = true,
- groupUuid = groupUuid,
- carrierName = "Carrier 1",
- profileClass = PROFILE_CLASS_PROVISIONING,
- )
+ fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() = runTest {
+ // This is a contrived test case, where the active subId is the one that would
+ // also be filtered by opportunistic filtering.
- val sub2 =
- SubscriptionModel(
- subscriptionId = 2,
- isOpportunistic = true,
- groupUuid = groupUuid,
- carrierName = "Carrier 2",
- profileClass = PROFILE_CLASS_UNSET,
- )
+ // GIVEN grouped, opportunistic subscriptions
+ val groupUuid = ParcelUuid(UUID.randomUUID())
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = true,
+ groupUuid = groupUuid,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
- // GIVEN active subId is 1
- connectionsRepository.setSubscriptions(listOf(sub1, sub2))
- connectionsRepository.setActiveMobileDataSubscriptionId(1)
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = true,
+ groupUuid = groupUuid,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
- // THEN filtering of provisioning subs takes place first, and we result in sub2
+ // GIVEN active subId is 1
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(1)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ // THEN filtering of provisioning subs takes place first, and we result in sub2
- assertThat(latest).isEqualTo(listOf(sub2))
- }
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(sub2))
+ }
@Test
fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() =
- kosmos.runTest {
+ runTest {
// Grouped filtering only happens when the list of subs is length 2. In this case
// we'll show that filtering of provisioning subs happens before, and thus grouped
// filtering happens even though the unfiltered list is length 3
@@ -384,87 +357,88 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() {
profileClass = PROFILE_CLASS_PROVISIONING,
)
- connectionsRepository.setSubscriptions(listOf(sub1, sub2, sub3))
- connectionsRepository.setActiveMobileDataSubscriptionId(1)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(1)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
assertThat(latest).isEqualTo(listOf(sub1))
}
@Test
- fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
- kosmos.runTest {
- val notExclusivelyNonTerrestrialSub =
- SubscriptionModel(
- isExclusivelyNonTerrestrial = false,
- subscriptionId = 5,
- carrierName = "Carrier 5",
- profileClass = PROFILE_CLASS_UNSET,
- )
+ fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = runTest {
+ val notExclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
- connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub))
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(notExclusivelyNonTerrestrialSub)
+ )
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
- }
+ assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+ }
@Test
- fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
- kosmos.runTest {
- val exclusivelyNonTerrestrialSub =
- SubscriptionModel(
- isExclusivelyNonTerrestrial = true,
- subscriptionId = 5,
- carrierName = "Carrier 5",
- profileClass = PROFILE_CLASS_UNSET,
- )
+ fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
- connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub))
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(exclusivelyNonTerrestrialSub)
+ )
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEmpty()
- }
+ assertThat(latest).isEmpty()
+ }
@Test
- fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
- kosmos.runTest {
- val exclusivelyNonTerrestrialSub =
- SubscriptionModel(
- isExclusivelyNonTerrestrial = true,
- subscriptionId = 5,
- carrierName = "Carrier 5",
- profileClass = PROFILE_CLASS_UNSET,
- )
- val otherSub1 =
- SubscriptionModel(
- isExclusivelyNonTerrestrial = false,
- subscriptionId = 1,
- carrierName = "Carrier 1",
- profileClass = PROFILE_CLASS_UNSET,
- )
- val otherSub2 =
- SubscriptionModel(
- isExclusivelyNonTerrestrial = false,
- subscriptionId = 2,
- carrierName = "Carrier 2",
- profileClass = PROFILE_CLASS_UNSET,
- )
-
- connectionsRepository.setSubscriptions(
- listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+ fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() = runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub1 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 1,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub2 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 2,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+ )
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
- assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
- }
+ assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+ }
@Test
fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
- kosmos.runTest {
+ runTest {
// Exclusively non-terrestrial sub
val exclusivelyNonTerrestrialSub =
SubscriptionModel(
@@ -483,10 +457,12 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() {
)
// WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
- connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub))
- connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(sub3, sub4, exclusivelyNonTerrestrialSub)
+ )
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
- val latest by collectLastValue(underTest.filteredSubscriptions)
+ val latest by underTest.filteredSubscriptions.collectLastValue()
// THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
// leaving only sub3.
@@ -494,484 +470,427 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() {
}
@Test
- fun activeDataConnection_turnedOn() =
- kosmos.runTest {
- (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
- as FakeMobileConnectionRepository)
- .dataEnabled
- .value = true
+ fun activeDataConnection_turnedOn() = runTest {
+ val connection1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[SUB_1_ID]!!
- val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
+ connection1.dataEnabled.setValue(true)
- assertThat(latest).isTrue()
- }
+ val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue()
+
+ assertThat(latest).isTrue()
+ }
@Test
- fun activeDataConnection_turnedOff() =
- kosmos.runTest {
- (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
- as FakeMobileConnectionRepository)
- .dataEnabled
- .value = true
+ fun activeDataConnection_turnedOff() = runTest {
+ val connection1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[SUB_1_ID]!!
- val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
+ connection1.dataEnabled.setValue(true)
+ val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue()
- (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
- as FakeMobileConnectionRepository)
- .dataEnabled
- .value = false
+ connection1.dataEnabled.setValue(false)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun activeDataConnection_invalidSubId() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
+ fun activeDataConnection_invalidSubId() = runTest {
+ val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue()
- connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(
+ INVALID_SUBSCRIPTION_ID
+ )
- // An invalid active subId should tell us that data is off
- assertThat(latest).isFalse()
- }
+ // An invalid active subId should tell us that data is off
+ assertThat(latest).isFalse()
+ }
@Test
- fun failedConnection_default_validated_notFailed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun failedConnection_default_validated_notFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = true
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun failedConnection_notDefault_notValidated_notFailed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun failedConnection_notDefault_notValidated_notFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.mobileIsDefault.value = false
- connectionsRepository.defaultConnectionIsValidated.value = false
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun failedConnection_default_notValidated_failed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun failedConnection_default_notValidated_failed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = false
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun failedConnection_carrierMergedDefault_notValidated_failed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun failedConnection_carrierMergedDefault_notValidated_failed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.hasCarrierMergedConnection.value = true
- connectionsRepository.defaultConnectionIsValidated.value = false
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
/** Regression test for b/275076959. */
@Test
- fun failedConnection_dataSwitchInSameGroup_notFailed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun failedConnection_dataSwitchInSameGroup_notFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = true
- runCurrent()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
- // WHEN there's a data change in the same subscription group
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
- connectionsRepository.defaultConnectionIsValidated.value = false
- runCurrent()
+ // WHEN there's a data change in the same subscription group
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
- // THEN the default connection is *not* marked as failed because of forced validation
- assertThat(latest).isFalse()
- }
+ // THEN the default connection is *not* marked as failed because of forced validation
+ assertThat(latest).isFalse()
+ }
@Test
- fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun failedConnection_dataSwitchNotInSameGroup_isFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = true
- runCurrent()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
- // WHEN the connection is invalidated without a activeSubChangedInGroupEvent
- connectionsRepository.defaultConnectionIsValidated.value = false
+ // WHEN the connection is invalidated without a activeSubChangedInGroupEvent
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
- // THEN the connection is immediately marked as failed
- assertThat(latest).isTrue()
- }
+ // THEN the connection is immediately marked as failed
+ assertThat(latest).isTrue()
+ }
@Test
- fun alwaysShowDataRatIcon_configHasTrue() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
+ fun alwaysShowDataRatIcon_configHasTrue() = runTest {
+ val latest by underTest.alwaysShowDataRatIcon.collectLastValue()
- val config = MobileMappings.Config()
- config.alwaysShowDataRatIcon = true
- connectionsRepository.defaultDataSubRatConfig.value = config
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = true
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun alwaysShowDataRatIcon_configHasFalse() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
+ fun alwaysShowDataRatIcon_configHasFalse() = runTest {
+ val latest by underTest.alwaysShowDataRatIcon.collectLastValue()
- val config = MobileMappings.Config()
- config.alwaysShowDataRatIcon = false
- connectionsRepository.defaultDataSubRatConfig.value = config
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = false
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun alwaysUseCdmaLevel_configHasTrue() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
+ fun alwaysUseCdmaLevel_configHasTrue() = runTest {
+ val latest by underTest.alwaysUseCdmaLevel.collectLastValue()
- val config = MobileMappings.Config()
- config.alwaysShowCdmaRssi = true
- connectionsRepository.defaultDataSubRatConfig.value = config
+ val config = MobileMappings.Config()
+ config.alwaysShowCdmaRssi = true
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun alwaysUseCdmaLevel_configHasFalse() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
+ fun alwaysUseCdmaLevel_configHasFalse() = runTest {
+ val latest by underTest.alwaysUseCdmaLevel.collectLastValue()
- val config = MobileMappings.Config()
- config.alwaysShowCdmaRssi = false
- connectionsRepository.defaultDataSubRatConfig.value = config
+ val config = MobileMappings.Config()
+ config.alwaysShowCdmaRssi = false
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun isSingleCarrier_zeroSubscriptions_false() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isSingleCarrier)
+ fun isSingleCarrier_zeroSubscriptions_false() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
- connectionsRepository.setSubscriptions(emptyList())
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(emptyList())
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun isSingleCarrier_oneSubscription_true() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isSingleCarrier)
+ fun isSingleCarrier_oneSubscription_true() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
- connectionsRepository.setSubscriptions(listOf(SUB_1))
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun isSingleCarrier_twoSubscriptions_false() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isSingleCarrier)
+ fun isSingleCarrier_twoSubscriptions_false() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun isSingleCarrier_updates() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isSingleCarrier)
+ fun isSingleCarrier_updates() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
- connectionsRepository.setSubscriptions(listOf(SUB_1))
- assertThat(latest).isTrue()
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
+ assertThat(latest).isTrue()
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- assertThat(latest).isFalse()
- }
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ assertThat(latest).isFalse()
+ }
@Test
- fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.mobileIsDefault)
+ fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
- connectionsRepository.mobileIsDefault.value = false
- connectionsRepository.hasCarrierMergedConnection.value = false
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(false)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.mobileIsDefault)
+ fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.hasCarrierMergedConnection.value = false
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(false)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
/** Regression test for b/272586234. */
@Test
- fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.mobileIsDefault)
+ fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
- connectionsRepository.mobileIsDefault.value = false
- connectionsRepository.hasCarrierMergedConnection.value = true
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun mobileIsDefault_updatesWhenRepoUpdates() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.mobileIsDefault)
+ fun mobileIsDefault_updatesWhenRepoUpdates() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
- connectionsRepository.mobileIsDefault.value = true
- assertThat(latest).isTrue()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ assertThat(latest).isTrue()
- connectionsRepository.mobileIsDefault.value = false
- assertThat(latest).isFalse()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ assertThat(latest).isFalse()
- connectionsRepository.hasCarrierMergedConnection.value = true
- assertThat(latest).isTrue()
- }
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true)
+ assertThat(latest).isTrue()
+ }
// The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
// is private and can only be tested by looking at [isDefaultConnectionFailed].
@Test
- fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
-
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = true
- runCurrent()
-
- // Trigger a data change in the same subscription group that's not yet validated
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
- connectionsRepository.defaultConnectionIsValidated.value = false
- runCurrent()
-
- // After 1s, the force validation bit is still present, so the connection is not marked
- // as failed
- testScope.advanceTimeBy(1000)
- assertThat(latest).isFalse()
-
- // After 2s, the force validation expires so the connection updates to failed
- testScope.advanceTimeBy(1001)
- assertThat(latest).isTrue()
- }
+ fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- @Test
- fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = false
- runCurrent()
+ // Trigger a data change in the same subscription group that's not yet validated
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+ // After 1s, the force validation bit is still present, so the connection is not marked
+ // as failed
+ testScope.advanceTimeBy(1000)
+ assertThat(latest).isFalse()
- assertThat(latest).isTrue()
- }
+ // After 2s, the force validation expires so the connection updates to failed
+ testScope.advanceTimeBy(1001)
+ assertThat(latest).isTrue()
+ }
@Test
- fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- // GIVEN the network starts validated
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = true
- runCurrent()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
- // WHEN a data change happens in the same group
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
- // WHEN the validation bit is lost
- connectionsRepository.defaultConnectionIsValidated.value = false
- runCurrent()
-
- // WHEN another data change happens in the same group
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
-
- // THEN the forced validation bit is still used...
- assertThat(latest).isFalse()
-
- testScope.advanceTimeBy(1000)
- assertThat(latest).isFalse()
-
- // ... but expires after 2s
- testScope.advanceTimeBy(1001)
- assertThat(latest).isTrue()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDefaultConnectionFailed)
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultConnectionIsValidated.value = true
- runCurrent()
+ fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+ // GIVEN the network starts validated
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
- testScope.advanceTimeBy(1000)
+ // WHEN a data change happens in the same group
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
- // WHEN another change in same group event happens
- connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
- connectionsRepository.defaultConnectionIsValidated.value = false
- runCurrent()
+ // WHEN the validation bit is lost
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
- // THEN the forced validation remains for exactly 2 more seconds from now
+ // WHEN another data change happens in the same group
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
- // 1.500s from second event
- testScope.advanceTimeBy(1500)
- assertThat(latest).isFalse()
+ // THEN the forced validation bit is still used...
+ assertThat(latest).isFalse()
- // 2.001s from the second event
- testScope.advanceTimeBy(501)
- assertThat(latest).isTrue()
- }
+ testScope.advanceTimeBy(1000)
+ assertThat(latest).isFalse()
- @Test
- fun isForceHidden_repoHasMobileHidden_true() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isForceHidden)
+ // ... but expires after 2s
+ testScope.advanceTimeBy(1001)
+ assertThat(latest).isTrue()
+ }
- kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+ @Test
+ fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
- assertThat(latest).isTrue()
- }
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
- @Test
- fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isForceHidden)
+ testScope.advanceTimeBy(1000)
- kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+ // WHEN another change in same group event happens
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
- assertThat(latest).isFalse()
- }
+ // THEN the forced validation remains for exactly 2 more seconds from now
- @Test
- fun iconInteractor_cachedPerSubId() =
- kosmos.runTest {
- val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
- val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+ // 1.500s from second event
+ testScope.advanceTimeBy(1500)
+ assertThat(latest).isFalse()
- assertThat(interactor1).isNotNull()
- assertThat(interactor1).isSameInstanceAs(interactor2)
- }
+ // 2.001s from the second event
+ testScope.advanceTimeBy(501)
+ assertThat(latest).isTrue()
+ }
@Test
- fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
+ fun isForceHidden_repoHasMobileHidden_true() = runTest {
+ val latest by underTest.isForceHidden.collectLastValue()
- connectionsRepository.isDeviceEmergencyCallCapable.value = true
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveMobileHidden_false() = runTest {
+ val latest by underTest.isForceHidden.collectLastValue()
- connectionsRepository.isDeviceEmergencyCallCapable.value = false
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun defaultDataSubId_tracksRepo() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.defaultDataSubId)
+ fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = runTest {
+ val latest by underTest.isDeviceInEmergencyCallsOnlyMode.collectLastValue()
- connectionsRepository.defaultDataSubId.value = 1
+ mobileConnectionsRepositoryKairos.fake.isDeviceEmergencyCallCapable.setValue(true)
- assertThat(latest).isEqualTo(1)
+ assertThat(latest).isTrue()
- connectionsRepository.defaultDataSubId.value = 2
+ mobileConnectionsRepositoryKairos.fake.isDeviceEmergencyCallCapable.setValue(false)
- assertThat(latest).isEqualTo(2)
- }
+ assertThat(latest).isFalse()
+ }
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun isStackable_tracksNumberOfSubscriptions() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isStackable)
+ fun isStackable_tracksNumberOfSubscriptions() = runTest {
+ val latest by underTest.isStackable.collectLastValue()
- connectionsRepository.setSubscriptions(listOf(SUB_1))
- assertThat(latest).isFalse()
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
+ assertThat(latest).isFalse()
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- assertThat(latest).isTrue()
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP))
- assertThat(latest).isFalse()
- }
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(SUB_1, SUB_2, SUB_3_OPP)
+ )
+ assertThat(latest).isFalse()
+ }
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun isStackable_checksForTerrestrialConnections() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isStackable)
+ fun isStackable_checksForTerrestrialConnections() = runTest {
+ val latest by underTest.isStackable.collectLastValue()
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- setNumberOfLevelsForSubId(SUB_1_ID, 5)
- setNumberOfLevelsForSubId(SUB_2_ID, 5)
- assertThat(latest).isTrue()
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ setNumberOfLevelsForSubId(SUB_1_ID, 5)
+ setNumberOfLevelsForSubId(SUB_2_ID, 5)
+ assertThat(latest).isTrue()
- (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
- as FakeMobileConnectionRepository)
- .isNonTerrestrial
- .value = true
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1_ID]!!
+ .isNonTerrestrial
+ .setValue(true)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun isStackable_checksForNumberOfBars() =
- kosmos.runTest {
- val latest by collectLastValue(underTest.isStackable)
+ fun isStackable_checksForNumberOfBars() = runTest {
+ val latest by underTest.isStackable.collectLastValue()
- // Number of levels is the same for both
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- setNumberOfLevelsForSubId(SUB_1_ID, 5)
- setNumberOfLevelsForSubId(SUB_2_ID, 5)
+ // Number of levels is the same for both
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ setNumberOfLevelsForSubId(SUB_1_ID, 5)
+ setNumberOfLevelsForSubId(SUB_2_ID, 5)
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- // Change the number of levels to be different than SUB_2
- setNumberOfLevelsForSubId(SUB_1_ID, 6)
+ // Change the number of levels to be different than SUB_2
+ setNumberOfLevelsForSubId(SUB_1_ID, 6)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
- private fun setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) {
- with(kosmos) {
- (fakeMobileConnectionsRepository.getRepoForSubId(subId)
- as FakeMobileConnectionRepository)
- .numberOfLevels
- .value = numberOfLevels
- }
+ private suspend fun KairosTestScope.setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) {
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[subId]!!
+ .numberOfLevels
+ .setValue(numberOfLevels)
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt
new file mode 100644
index 000000000000..57e63a595b8f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
+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.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LocationBasedMobileIconViewModelKairosTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private lateinit var commonImpl: MobileIconViewModelCommonKairos
+ private lateinit var homeIcon: HomeMobileIconViewModelKairos
+ private lateinit var qsIcon: QsMobileIconViewModelKairos
+ private lateinit var keyguardIcon: KeyguardMobileIconViewModelKairos
+ private lateinit var iconsInteractor: MobileIconsInteractor
+ private lateinit var interactor: MobileIconInteractor
+ private val connectionsRepository = kosmos.fakeMobileConnectionsRepository
+ private lateinit var repository: FakeMobileConnectionRepository
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val flags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+
+ @Mock private lateinit var constants: ConnectivityConstants
+ private val tableLogBuffer =
+ logcatTableLogBuffer(kosmos, "LocationBasedMobileIconViewModelTest")
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ connectionsRepository,
+ )
+ repository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
+ isInService.value = true
+ cdmaLevel.value = 1
+ primaryLevel.value = 1
+ isEmergencyOnly.value = false
+ numberOfLevels.value = 4
+ resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G")
+ dataConnectionState.value = DataConnectionState.Connected
+ }
+
+ connectionsRepository.activeMobileDataRepository.value = repository
+
+ connectivityRepository.apply { setMobileConnected() }
+
+ iconsInteractor =
+ MobileIconsInteractorImpl(
+ connectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ flags,
+ )
+
+ interactor =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ iconsInteractor.activeDataConnectionHasDataEnabled,
+ iconsInteractor.alwaysShowDataRatIcon,
+ iconsInteractor.alwaysUseCdmaLevel,
+ iconsInteractor.isSingleCarrier,
+ iconsInteractor.mobileIsDefault,
+ iconsInteractor.defaultMobileIconMapping,
+ iconsInteractor.defaultMobileIconGroup,
+ iconsInteractor.isDefaultConnectionFailed,
+ iconsInteractor.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake(),
+ )
+
+ commonImpl =
+ MobileIconViewModelKairos(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+
+ homeIcon = HomeMobileIconViewModelKairos(commonImpl, mock())
+ qsIcon = QsMobileIconViewModelKairos(commonImpl)
+ keyguardIcon = KeyguardMobileIconViewModelKairos(commonImpl)
+ }
+
+ @Test
+ fun locationBasedViewModelsReceiveSameIconIdWhenCommonImplUpdates() =
+ testScope.runTest {
+ var latestHome: SignalIconModel? = null
+ val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
+
+ var latestQs: SignalIconModel? = null
+ val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
+
+ var latestKeyguard: SignalIconModel? = null
+ val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
+
+ var expected = defaultSignal(level = 1)
+
+ assertThat(latestHome).isEqualTo(expected)
+ assertThat(latestQs).isEqualTo(expected)
+ assertThat(latestKeyguard).isEqualTo(expected)
+
+ repository.setAllLevels(2)
+ expected = defaultSignal(level = 2)
+
+ assertThat(latestHome).isEqualTo(expected)
+ assertThat(latestQs).isEqualTo(expected)
+ assertThat(latestKeyguard).isEqualTo(expected)
+
+ homeJob.cancel()
+ qsJob.cancel()
+ keyguardJob.cancel()
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+ private const val NUM_LEVELS = 4
+
+ /** Convenience constructor for these tests */
+ fun defaultSignal(level: Int = 1): SignalIconModel {
+ return SignalIconModel.Cellular(
+ level,
+ NUM_LEVELS,
+ showExclamationMark = false,
+ carrierNetworkChange = false,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt
new file mode 100644
index 000000000000..6b114a8256f2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons.G
+import com.android.settingslib.mobile.TelephonyIcons.THREE_G
+import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+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.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconViewModelKairosTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private var connectivityRepository = FakeConnectivityRepository()
+
+ private lateinit var underTest: MobileIconViewModelKairos
+ private lateinit var interactor: MobileIconInteractorImpl
+ private lateinit var iconsInteractor: MobileIconsInteractorImpl
+ private lateinit var repository: FakeMobileConnectionRepository
+ private lateinit var connectionsRepository: FakeMobileConnectionsRepository
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+ @Mock private lateinit var constants: ConnectivityConstants
+ private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconViewModelTest")
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+ private val flags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+
+ connectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+
+ repository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
+ setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+ connectionsRepository.activeMobileDataRepository.value = repository
+ connectionsRepository.mobileIsDefault.value = true
+
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ kosmos.fakeMobileConnectionsRepository,
+ )
+
+ iconsInteractor =
+ MobileIconsInteractorImpl(
+ connectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ flags,
+ )
+
+ interactor =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ iconsInteractor.activeDataConnectionHasDataEnabled,
+ iconsInteractor.alwaysShowDataRatIcon,
+ iconsInteractor.alwaysUseCdmaLevel,
+ iconsInteractor.isSingleCarrier,
+ iconsInteractor.mobileIsDefault,
+ iconsInteractor.defaultMobileIconMapping,
+ iconsInteractor.defaultMobileIconGroup,
+ iconsInteractor.isDefaultConnectionFailed,
+ iconsInteractor.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake(),
+ )
+ createAndSetViewModel()
+ }
+
+ @Test
+ fun isVisible_notDataCapable_alwaysFalse() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.hasDataCapabilities).thenReturn(false)
+ createAndSetViewModel()
+
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_notAirplane_notForceHidden_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_airplaneAndNotAllowed_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ repository.isAllowedDuringAirplaneMode.value = false
+ connectivityRepository.setForceHiddenIcons(setOf())
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ /** Regression test for b/291993542. */
+ @Test
+ fun isVisible_airplaneButAllowed_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ repository.isAllowedDuringAirplaneMode.value = true
+ connectivityRepository.setForceHiddenIcons(setOf())
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_forceHidden_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_respondsToUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ connectivityRepository.setForceHiddenIcons(setOf())
+
+ assertThat(latest).isTrue()
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ assertThat(latest).isFalse()
+
+ repository.isAllowedDuringAirplaneMode.value = true
+ assertThat(latest).isTrue()
+
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_satellite_respectsAirplaneMode() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isVisible)
+
+ repository.isNonTerrestrial.value = true
+ airplaneModeInteractor.setIsAirplaneMode(false)
+
+ assertThat(latest).isTrue()
+
+ airplaneModeInteractor.setIsAirplaneMode(true)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun contentDescription_notInService_usesNoPhone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.isInService.value = false
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
+
+ @Test
+ fun contentDescription_includesNetworkName() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.isInService.value = true
+ repository.networkName.value = NetworkNameModel.SubscriptionDerived("Test Network Name")
+ repository.numberOfLevels.value = 5
+ repository.setAllLevels(3)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular("Test Network Name", THREE_BARS))
+ }
+
+ @Test
+ fun contentDescription_inService_usesLevel() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.setAllLevels(2)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
+
+ repository.setAllLevels(0)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
+
+ @Test
+ fun contentDescription_nonInflated_invalidLevelUsesNoSignalText() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = false
+ repository.setAllLevels(-1)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+
+ repository.setAllLevels(100)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
+
+ @Test
+ fun contentDescription_nonInflated_levelStrings() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = false
+ repository.setAllLevels(0)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+
+ repository.setAllLevels(1)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR))
+
+ repository.setAllLevels(2)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
+
+ repository.setAllLevels(3)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS))
+
+ repository.setAllLevels(4)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS))
+ }
+
+ @Test
+ fun contentDescription_inflated_invalidLevelUsesNoSignalText() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = true
+ repository.numberOfLevels.value = 6
+
+ repository.setAllLevels(-2)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+
+ repository.setAllLevels(100)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
+
+ @Test
+ fun contentDescription_inflated_levelStrings() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = true
+ repository.numberOfLevels.value = 6
+
+ // Note that the _repo_ level is 1 lower than the reported level through the interactor
+
+ repository.setAllLevels(0)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR))
+
+ repository.setAllLevels(1)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
+
+ repository.setAllLevels(2)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS))
+
+ repository.setAllLevels(3)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FOUR_BARS))
+
+ repository.setAllLevels(4)
+
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS))
+ }
+
+ @Test
+ fun contentDescription_nonInflated_testABunchOfLevelsForNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = false
+ repository.numberOfLevels.value = 5
+
+ // -1 and 5 are out of the bounds for non-inflated content descriptions
+ for (i in -1..5) {
+ repository.setAllLevels(i)
+ when (i) {
+ -1,
+ 5 ->
+ assertWithMessage("Level $i is expected to be 'no signal'")
+ .that((latest as MobileContentDescription.Cellular).levelDescriptionRes)
+ .isEqualTo(NO_SIGNAL)
+ else ->
+ assertWithMessage("Level $i is expected not to be null")
+ .that(latest)
+ .isNotNull()
+ }
+ }
+ }
+
+ @Test
+ fun contentDescription_inflated_testABunchOfLevelsForNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+ repository.inflateSignalStrength.value = true
+ repository.numberOfLevels.value = 6
+ // -1 and 6 are out of the bounds for inflated content descriptions
+ // Note that the interactor adds 1 to the reported level, hence the -2 to 5 range
+ for (i in -2..5) {
+ repository.setAllLevels(i)
+ when (i) {
+ -2,
+ 5 ->
+ assertWithMessage("Level $i is expected to be 'no signal'")
+ .that((latest as MobileContentDescription.Cellular).levelDescriptionRes)
+ .isEqualTo(NO_SIGNAL)
+ else ->
+ assertWithMessage("Level $i is not expected to be null")
+ .that(latest)
+ .isNotNull()
+ }
+ }
+ }
+
+ @Test
+ fun networkType_dataEnabled_groupIsRepresented() =
+ testScope.runTest {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ connectionsRepository.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_null_whenDisabled() =
+ testScope.runTest {
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.setDataEnabled(false)
+ connectionsRepository.mobileIsDefault.value = true
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_null_whenCarrierNetworkChangeActive() =
+ testScope.runTest {
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.carrierNetworkChangeActive.value = true
+ connectionsRepository.mobileIsDefault.value = true
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkTypeIcon_notNull_whenEnabled() =
+ testScope.runTest {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.setDataEnabled(true)
+ repository.dataConnectionState.value = DataConnectionState.Connected
+ connectionsRepository.mobileIsDefault.value = true
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_nullWhenDataDisconnects() =
+ testScope.runTest {
+ val initial =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(initial)
+
+ repository.dataConnectionState.value = DataConnectionState.Disconnected
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_null_changeToDisabled() =
+ testScope.runTest {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ repository.dataEnabled.value = true
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(expected)
+
+ repository.dataEnabled.value = false
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenDisabled() =
+ testScope.runTest {
+ repository.dataEnabled.value = false
+
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenDisconnected() =
+ testScope.runTest {
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.dataConnectionState.value = DataConnectionState.Disconnected
+
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
+ testScope.runTest {
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_usesDefaultIconWhenInvalid() =
+ testScope.runTest {
+ // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the
+ // old pipeline was to use the default icon group if the map doesn't exist
+ repository.setNetworkTypeKey(UNKNOWN.name)
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ connectionsRepository.defaultMobileIconGroup.value.dataType,
+ ContentDescription.Resource(G.dataContentDescription),
+ )
+
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownWhenNotDefault() =
+ testScope.runTest {
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_notShownWhenNotDefault() =
+ testScope.runTest {
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.dataConnectionState.value = DataConnectionState.Connected
+ connectionsRepository.mobileIsDefault.value = false
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun roaming() =
+ testScope.runTest {
+ repository.setAllRoaming(true)
+
+ var latest: Boolean? = null
+ val job = underTest.roaming.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ repository.setAllRoaming(false)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataActivity_nullWhenConfigIsOff() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+
+ var inVisible: Boolean? = null
+ val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+ var outVisible: Boolean? = null
+ val outJob = underTest.activityInVisible.onEach { outVisible = it }.launchIn(this)
+
+ var containerVisible: Boolean? = null
+ val containerJob =
+ underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isFalse()
+
+ inJob.cancel()
+ outJob.cancel()
+ containerJob.cancel()
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun dataActivity_configOn_testIndicators_staticFlagOff() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+
+ var inVisible: Boolean? = null
+ val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+ var outVisible: Boolean? = null
+ val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+
+ var containerVisible: Boolean? = null
+ val containerJob =
+ underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+
+ yield()
+
+ assertThat(inVisible).isTrue()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isTrue()
+ assertThat(containerVisible).isTrue()
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isFalse()
+
+ inJob.cancel()
+ outJob.cancel()
+ containerJob.cancel()
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun dataActivity_configOn_testIndicators_staticFlagOn() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+
+ var inVisible: Boolean? = null
+ val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+ var outVisible: Boolean? = null
+ val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+
+ var containerVisible: Boolean? = null
+ val containerJob =
+ underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+
+ yield()
+
+ assertThat(inVisible).isTrue()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isTrue()
+ assertThat(containerVisible).isTrue()
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+
+ inJob.cancel()
+ outJob.cancel()
+ containerJob.cancel()
+ }
+
+ @Test
+ fun netTypeBackground_nullWhenNoPrioritizedCapabilities() =
+ testScope.runTest {
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun netTypeBackground_sliceUiEnabled_notNullWhenPrioritizedCapabilities_newIcons() =
+ testScope.runTest {
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest)
+ .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background_updated, null))
+ }
+
+ @Test
+ @DisableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun netTypeBackground_sliceUiDisabled_notNullWhenPrioritizedCapabilities_oldIcons() =
+ testScope.runTest {
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest)
+ .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
+ }
+
+ @Test
+ fun nonTerrestrial_defaultProperties() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+
+ val roaming by collectLastValue(underTest.roaming)
+ val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+ val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+ val activityInVisible by collectLastValue(underTest.activityInVisible)
+ val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+ val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
+
+ @Test
+ fun nonTerrestrial_ignoresDefaultProperties() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+
+ val roaming by collectLastValue(underTest.roaming)
+ val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+ val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+ val activityInVisible by collectLastValue(underTest.activityInVisible)
+ val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+ val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+ repository.setAllRoaming(true)
+ repository.setNetworkTypeKey(connectionsRepository.LTE_KEY)
+ // sets the background on cellular
+ repository.hasPrioritizedNetworkCapabilities.value = true
+ repository.dataActivityDirection.value =
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
+
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun nonTerrestrial_usesSatelliteIcon_flagOff() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+ repository.setAllLevels(0)
+ repository.satelliteLevel.value = 0
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ repository.satelliteLevel.value = 1
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ repository.satelliteLevel.value = 2
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ repository.satelliteLevel.value = 3
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ repository.satelliteLevel.value = 4
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun nonTerrestrial_usesSatelliteIcon_flagOn() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+ repository.satelliteLevel.value = 0
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.satelliteLevel.value = 1
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.satelliteLevel.value = 2
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.satelliteLevel.value = 3
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.satelliteLevel.value = 4
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satelliteIcon_ignoresInflateSignalStrength_flagOff() =
+ testScope.runTest {
+ // Note that this is the exact same test as above, but with inflateSignalStrength set to
+ // true we note that the level is unaffected by inflation
+ repository.inflateSignalStrength.value = true
+ repository.isNonTerrestrial.value = true
+ repository.setAllLevels(0)
+ repository.satelliteLevel.value = 0
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ repository.satelliteLevel.value = 1
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ repository.satelliteLevel.value = 2
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ repository.satelliteLevel.value = 3
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ repository.satelliteLevel.value = 4
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satelliteIcon_ignoresInflateSignalStrength_flagOn() =
+ testScope.runTest {
+ // Note that this is the exact same test as above, but with inflateSignalStrength set to
+ // true we note that the level is unaffected by inflation
+ repository.inflateSignalStrength.value = true
+ repository.isNonTerrestrial.value = true
+ repository.satelliteLevel.value = 0
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.satelliteLevel.value = 1
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.satelliteLevel.value = 2
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.satelliteLevel.value = 3
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.satelliteLevel.value = 4
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
+ private fun createAndSetViewModel() {
+ underTest =
+ MobileIconViewModelKairos(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+
+ // For convenience, just define these as constants
+ private val NO_SIGNAL = R.string.accessibility_no_signal
+ private val ONE_BAR = R.string.accessibility_one_bar
+ private val TWO_BARS = R.string.accessibility_two_bars
+ private val THREE_BARS = R.string.accessibility_three_bars
+ private val FOUR_BARS = R.string.accessibility_four_bars
+ private val FULL_BARS = R.string.accessibility_signal_full
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt
new file mode 100644
index 000000000000..e921430394c2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.statusbar.phone.StatusBarLocation
+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.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconsViewModelKairosTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: MobileIconsViewModelKairos
+ private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic()
+
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+ @Mock private lateinit var constants: ConnectivityConstants
+ @Mock private lateinit var logger: MobileViewLogger
+ @Mock private lateinit var verboseLogger: VerboseMobileViewLogger
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ kosmos.fakeMobileConnectionsRepository,
+ )
+
+ underTest =
+ MobileIconsViewModelKairos(
+ logger,
+ verboseLogger,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ }
+
+ @Test
+ fun subscriptionIdsFlow_matchesInteractor() =
+ testScope.runTest {
+ var latest: List<Int>? = null
+ val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value =
+ listOf(
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ )
+ assertThat(latest).isEqualTo(listOf(1))
+
+ interactor.filteredSubscriptions.value =
+ listOf(
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ SubscriptionModel(
+ subscriptionId = 5,
+ isOpportunistic = true,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ SubscriptionModel(
+ subscriptionId = 7,
+ isOpportunistic = true,
+ carrierName = "Carrier 7",
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ )
+ assertThat(latest).isEqualTo(listOf(2, 5, 7))
+
+ interactor.filteredSubscriptions.value = emptyList()
+ assertThat(latest).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun caching_mobileIconViewModelIsReusedForSameSubId() =
+ testScope.runTest {
+ val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+ val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS)
+
+ assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl)
+ }
+
+ @Test
+ fun caching_invalidViewModelsAreRemovedFromCacheWhenSubDisappears() =
+ testScope.runTest {
+ // Retrieve models to trigger caching
+ val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+ val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+
+ // Both impls are cached
+ assertThat(underTest.reuseCache.keys).containsExactly(1, 2)
+
+ // SUB_1 is removed from the list...
+ interactor.filteredSubscriptions.value = listOf(SUB_2)
+
+ // ... and dropped from the cache
+ assertThat(underTest.reuseCache.keys).containsExactly(2)
+ }
+
+ @Test
+ fun caching_invalidatedViewModelsAreCanceled() =
+ testScope.runTest {
+ // Retrieve models to trigger caching
+ val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+ val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+
+ var scope1 = underTest.reuseCache[1]?.second
+ var scope2 = underTest.reuseCache[2]?.second
+
+ // Scopes are not canceled
+ assertTrue(scope1!!.isActive)
+ assertTrue(scope2!!.isActive)
+
+ // SUB_1 is removed from the list...
+ interactor.filteredSubscriptions.value = listOf(SUB_2)
+
+ // scope1 is canceled
+ assertFalse(scope1!!.isActive)
+ assertTrue(scope2!!.isActive)
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = emptyList()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1)
+ // The unknown icon group doesn't show a RAT
+ interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1)
+ // The 3G icon group will show a RAT
+ interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1)
+ val sub1Interactor = interactor.getInteractorForSubId(1)!!
+
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ assertThat(latest).isTrue()
+
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ assertThat(latest).isFalse()
+
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.LTE)
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+ assertThat(latest).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+ assertThat(latest).isTrue()
+
+ // WHEN the sub list gets new subscriptions where the last subscription is not showing
+ // the network type icon
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+ interactor.getInteractorForSubId(3)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+ // THEN the flow updates
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ // Immediately switch the order so that we've created both interactors
+ interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
+ val sub1Interactor = interactor.getInteractorForSubId(1)!!
+ val sub2Interactor = interactor.getInteractorForSubId(2)!!
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ sub2Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ assertThat(latest).isTrue()
+
+ // WHEN sub1 becomes last and sub1 has no network type icon
+ interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
+
+ // THEN the flow updates
+ assertThat(latest).isFalse()
+
+ // WHEN sub2 becomes last and sub2 has a network type icon
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+
+ // THEN the flow updates
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ companion object {
+ private val SUB_1 =
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_3 =
+ SubscriptionModel(
+ subscriptionId = 3,
+ isOpportunistic = false,
+ carrierName = "Carrier 3",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt
new file mode 100644
index 000000000000..ce35d9d8610f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StackedMobileIconViewModelKairosTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+
+ private val Kosmos.underTest: StackedMobileIconViewModelKairos by Fixture {
+ stackedMobileIconViewModelKairos
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.underTest.activateIn(testScope)
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_filtersOutNonDualConnections() =
+ kosmos.runTest {
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf()
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ assertThat(underTest.dualSim).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_filtersOutNonCellularIcons() =
+ kosmos.runTest {
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ assertThat(underTest.dualSim).isNull()
+
+ fakeMobileIconsInteractor
+ .getInteractorForSubId(SUB_1.subscriptionId)!!
+ .signalLevelIcon
+ .value =
+ SignalIconModel.Satellite(
+ level = 0,
+ icon = Icon.Resource(res = 0, contentDescription = null),
+ )
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ assertThat(underTest.dualSim).isNull()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun dualSim_tracksActiveSubId() =
+ kosmos.runTest {
+ // Active sub id is null, order is unchanged
+ fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ setIconLevel(SUB_1.subscriptionId, 1)
+ setIconLevel(SUB_2.subscriptionId, 2)
+
+ assertThat(underTest.dualSim!!.primary.level).isEqualTo(1)
+ assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2)
+
+ // Active sub is 2, order is swapped
+ fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId
+
+ assertThat(underTest.dualSim!!.primary.level).isEqualTo(2)
+ assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1)
+ }
+
+ private fun setIconLevel(subId: Int, level: Int) {
+ with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) {
+ signalLevelIcon.value =
+ (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level)
+ }
+ }
+
+ companion object {
+ private val SUB_1 =
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ private val SUB_3 =
+ SubscriptionModel(
+ subscriptionId = 3,
+ isOpportunistic = false,
+ carrierName = "Carrier 3",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index f91e3a612862..39cf02dbc772 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import kotlinx.coroutines.flow.Flow
@@ -52,7 +53,8 @@ class FakeHomeStatusBarViewModel(
override val primaryOngoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
MutableStateFlow(OngoingActivityChipModel.Inactive())
- override val ongoingActivityChips = MutableStateFlow(MultipleOngoingActivityChipsModel())
+ override val ongoingActivityChips =
+ ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false)
override val ongoingActivityChipsLegacy =
MutableStateFlow(MultipleOngoingActivityChipsModelLegacy())
@@ -62,7 +64,7 @@ class FakeHomeStatusBarViewModel(
override val mediaProjectionStopDialogDueToCallEndedState =
MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
- override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
+ override val isHomeStatusBarAllowed = MutableStateFlow(false)
override val canShowOngoingActivityChips: Flow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 7e8ee1b156df..20cf3ae6b8dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -30,6 +30,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.fake
@@ -48,6 +49,7 @@ import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.log.assertLogsWtf
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -57,6 +59,7 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
@@ -64,6 +67,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaPr
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsCallChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
import com.android.systemui.statusbar.core.StatusBarRootModernization
@@ -88,6 +92,7 @@ import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
@@ -95,7 +100,6 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -104,7 +108,8 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class HomeStatusBarViewModelImplTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
+ private val Kosmos.underTest by
+ Kosmos.Fixture { kosmos.homeStatusBarViewModel.also { it.activateIn(kosmos.testScope) } }
@Before
fun setUp() {
@@ -500,9 +505,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneLockscreen_notOccluded_false() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_sceneLockscreen_notOccluded_false() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
@@ -511,9 +517,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneLockscreen_occluded_true() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_sceneLockscreen_occluded_true() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
@@ -522,9 +529,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_overlayBouncer_false() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_overlayBouncer_false() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
kosmos.sceneContainerRepository.showOverlay(Overlays.Bouncer)
@@ -533,9 +541,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneCommunal_false() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_sceneCommunal_false() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Communal)
@@ -543,9 +552,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneShade_false() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_sceneShade_false() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
@@ -553,9 +563,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneGone_true() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_sceneGone_true() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
@@ -563,9 +574,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneGoneWithNotificationsShadeOverlay_false() =
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_sceneGoneWithNotificationsShadeOverlay_false() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
kosmos.sceneContainerRepository.showOverlay(Overlays.NotificationsShade)
@@ -575,12 +587,102 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun isHomeStatusBarAllowedByScene_sceneGoneWithQuickSettingsShadeOverlay_false() =
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_QsVisibleButInExternalDisplay_defaultStatusBarVisible() =
kosmos.runTest {
- val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
+ kosmos.fakeShadeDisplaysRepository.setDisplayId(EXTERNAL_DISPLAY)
+ runCurrent()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_QsVisibleButInExternalDisplay_withFlagOff_defaultStatusBarInvisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
+
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
+ kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
+ kosmos.fakeShadeDisplaysRepository.setDisplayId(EXTERNAL_DISPLAY)
+ runCurrent()
+
+ // Shade position is ignored.
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_qsVisibleInThisDisplay_thisStatusBarInvisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
+
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
+ kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
+ kosmos.fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)
+ runCurrent()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_qsExpandedOnDefaultDisplay_statusBarInAnotherDisplay_visible() =
+ kosmos.runTest {
+ val underTest = homeStatusBarViewModelFactory(EXTERNAL_DISPLAY)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
+
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
+ kosmos.sceneContainerRepository.showOverlay(Overlays.QuickSettingsShade)
+ runCurrent()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isHomeStatusBarAllowed_onDefaultDisplayLockscreen_invisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
+
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ runCurrent()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun isHomeStatusBarAllowed_onExternalDispalyWithLocksceren_invisible() =
+ kosmos.runTest {
+ val underTest = homeStatusBarViewModelFactory(EXTERNAL_DISPLAY)
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
+
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ runCurrent()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun isHomeStatusBarAllowed_legacy_onDefaultDisplayLockscreen_invisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isHomeStatusBarAllowed)
+
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.GONE,
+ KeyguardState.LOCKSCREEN,
+ )
+
runCurrent()
assertThat(latest).isFalse()
@@ -716,7 +818,26 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
- fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hun_false() =
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_true() =
kosmos.runTest {
val latest by collectLastValue(underTest.canShowOngoingActivityChips)
@@ -729,7 +850,162 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- assertThat(latest).isFalse()
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarHidden_noSecureCamera_noHun_notAllowed() =
+ kosmos.runTest {
+ // home status bar not allowed
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_isAllowed() =
+ kosmos.runTest {
+ transitionKeyguardToGone()
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_secureCamera_noHun_notAllowed() =
+ kosmos.runTest {
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_notAllowed() =
+ kosmos.runTest {
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_isAllowed() =
+ kosmos.runTest {
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() =
+ kosmos.runTest {
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_isAllowed() =
+ kosmos.runTest {
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_followsChipsViewModel() =
+ kosmos.runTest {
+ transitionKeyguardToGone()
+
+ screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertIsScreenRecordChip(underTest.ongoingActivityChips.chips.active[0])
+
+ addOngoingCallState(key = "call")
+
+ assertIsScreenRecordChip(underTest.ongoingActivityChips.chips.active[0])
+ assertIsCallChip(underTest.ongoingActivityChips.chips.active[1], "call", context)
}
@Test
@@ -892,7 +1168,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableChipsModernization
- fun isNotificationIconContainerVisible_anyChipShowing_ChipsModernizationOn() =
+ fun isNotificationIconContainerVisible_anyChipShowing_chipsModernizationOn() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
@@ -909,7 +1185,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun isNotificationIconContainerVisible_anyChipShowing_PromotedNotifsOn() =
+ fun isNotificationIconContainerVisible_anyChipShowing_promotedNotifsOn() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
@@ -929,7 +1205,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
StatusBarRootModernization.FLAG_NAME,
StatusBarChipsModernization.FLAG_NAME,
)
- fun isNotificationIconContainerVisible_anyChipShowing_ChipsModernizationAndPromotedNotifsOff() =
+ fun isNotificationIconContainerVisible_anyChipShowing_chipsModernizationAndPromotedNotifsOff() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
@@ -943,6 +1219,86 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
}
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunBySystem_noHunFlagOff_visible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedBySystem
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunByUser_noHunFlagOff_gone() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedByUser
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunBySystem_noHunFlagOn_gone() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedBySystem
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunByUser_noHunFlagOn_gone() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedByUser
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ }
+
@Test
fun isSystemInfoVisible_allowedByDisableFlags_visible() =
kosmos.runTest {
@@ -1144,7 +1500,39 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
- fun shadeShown_sceneFlagOff_noStatusBarViewsShown() =
+ fun shadeSlightlyShown_sceneFlagOff_statusBarViewsShown() =
+ kosmos.runTest {
+ val clockVisible by collectLastValue(underTest.isClockVisible)
+ val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
+ transitionKeyguardToGone()
+
+ kosmos.shadeTestUtil.setShadeExpansion(0.1f)
+
+ assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun shadeHalfShown_sceneFlagOff_noStatusBarViewsShown() =
+ kosmos.runTest {
+ val clockVisible by collectLastValue(underTest.isClockVisible)
+ val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
+ transitionKeyguardToGone()
+
+ kosmos.shadeTestUtil.setShadeExpansion(0.5f)
+
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun shadeFullyShown_sceneFlagOff_noStatusBarViewsShown() =
kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
@@ -1158,6 +1546,83 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
+ /** Regression test for b/394257529#comment24. */
+ @Test
+ @DisableSceneContainer
+ fun qqsToQsTransition_sceneFlagOff_statusBarViewsNeverShown() =
+ kosmos.runTest {
+ val clockVisible by collectLastValue(underTest.isClockVisible)
+ val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
+ transitionKeyguardToGone()
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 1f, qsExpansion = 0f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.9f, qsExpansion = 0.1f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.6f, qsExpansion = 0.4f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.5f, qsExpansion = 0.5f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.2f, qsExpansion = 0.8f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0f, qsExpansion = 1f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+ }
+
+ /** Regression test for b/394257529#comment24. */
+ @Test
+ @DisableSceneContainer
+ fun qsToQqsTransition_sceneFlagOff_statusBarViewsNeverShown() =
+ kosmos.runTest {
+ val clockVisible by collectLastValue(underTest.isClockVisible)
+ val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+ val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
+ transitionKeyguardToGone()
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0f, qsExpansion = 1f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.3f, qsExpansion = 0.7f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.5f, qsExpansion = 0.5f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 0.7f, qsExpansion = 0.3f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+
+ kosmos.shadeTestUtil.setShadeAndQsExpansion(shadeExpansion = 1f, qsExpansion = 0f)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
+ }
+
@Test
@EnableSceneContainer
fun shadeShown_sceneFlagOn_noStatusBarViewsShown() =
@@ -1289,4 +1754,8 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
testScope = testScope,
)
}
+
+ private companion object {
+ const val EXTERNAL_DISPLAY = 1
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
index 769f012bfdf7..8722a484417d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
@@ -37,7 +38,7 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val fakeDisplayRepository = kosmos.displayRepository
private val testScope = kosmos.testScope
@@ -59,11 +60,11 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 9a0b8125fb25..b8be34378891 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -282,6 +282,33 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.TONAL_SPOT);
}
+ @Test
+ @HardwareColors(color = "BLK", options = {
+ "BLK|MONOCHROMATIC|#FF0000",
+ "*|VIBRANT|home_wallpaper"
+ })
+ @EnableFlags(com.android.systemui.Flags.FLAG_HARDWARE_COLOR_STYLES)
+ public void start_checkHardwareColor_storeInSecureSetting() {
+ // getWallpaperColors should not be called
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainExecutor).execute(registrationRunnable.capture());
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager, never()).getWallpaperColors(anyInt());
+
+ assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.MONOCHROMATIC);
+ assertThat(mThemeOverlayController.mCurrentColors.get(0).getMainColors().get(
+ 0).toArgb()).isEqualTo(Color.RED);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+ anyInt());
+
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.theme_style\":\"MONOCHROMATIC")).isTrue();
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.system_palette\":\"ffff0000")).isTrue();
+ }
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
new file mode 100644
index 000000000000..6d3813c90bfd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects
+
+import android.view.View
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyevent.data.repository.keyEventRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository
+import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
+import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot
+import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TopLevelWindowEffectsTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ @Mock
+ private lateinit var windowManager: WindowManager
+
+ @Mock
+ private lateinit var viewCapture: Lazy<ViewCapture>
+
+ @Mock
+ private lateinit var viewModelFactory: SqueezeEffectViewModel.Factory
+
+ private val Kosmos.underTest by Kosmos.Fixture {
+ TopLevelWindowEffects(
+ context = mContext,
+ applicationScope = testScope.backgroundScope,
+ windowManager = ViewCaptureAwareWindowManager(
+ windowManager = windowManager,
+ lazyViewCapture = viewCapture,
+ isViewCaptureEnabled = false
+ ),
+ keyEventInteractor = keyEventInteractor,
+ viewModelFactory = viewModelFactory,
+ squeezeEffectInteractor = SqueezeEffectInteractor(
+ squeezeEffectRepository = fakeSqueezeEffectRepository
+ )
+ )
+ }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ doNothing().whenever(windowManager).addView(any<View>(), any<WindowManager.LayoutParams>())
+ doNothing().whenever(windowManager).removeView(any<View>())
+ doNothing().whenever(windowManager).removeView(any<EffectsWindowRoot>())
+ }
+
+ @Test
+ fun noWindowWhenSqueezeEffectDisabled() =
+ kosmos.runTest {
+ fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = false
+
+ underTest.start()
+
+ verify(windowManager, never()).addView(any<View>(), any<WindowManager.LayoutParams>())
+ }
+
+ @Test
+ fun addViewToWindowWhenSqueezeEffectEnabled() =
+ kosmos.runTest {
+ fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
+ fakeKeyEventRepository.setPowerButtonDown(true)
+
+ underTest.start()
+
+ verify(windowManager, times(1)).addView(any<View>(),
+ any<WindowManager.LayoutParams>())
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt
new file mode 100644
index 000000000000..9b01fd3242e5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.data.repository
+
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shared.Flags
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+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)
+class SqueezeEffectRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val globalSettings = FakeGlobalSettings(StandardTestDispatcher())
+
+ @Mock
+ private lateinit var bgHandler: Handler
+
+ private val Kosmos.underTest by Kosmos.Fixture {
+ SqueezeEffectRepositoryImpl(
+ bgHandler = bgHandler,
+ bgCoroutineContext = testScope.testScheduler,
+ globalSettings = globalSettings
+ )
+ }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT)
+ @Test
+ fun testSqueezeEffectDisabled_WhenFlagDisabled() =
+ kosmos.runTest {
+ val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+ assertThat(isSqueezeEffectEnabled).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT)
+ @Test
+ fun testSqueezeEffectDisabled_WhenFlagEnabled_GlobalSettingsDisabled() =
+ kosmos.runTest {
+ globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 0)
+
+ val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+ assertThat(isSqueezeEffectEnabled).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT)
+ @Test
+ fun testSqueezeEffectEnabled_WhenFlagEnabled_GlobalSettingEnabled() =
+ kosmos.runTest {
+ globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
+
+ val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+ assertThat(isSqueezeEffectEnabled).isTrue()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt
new file mode 100644
index 000000000000..a94d49c5d40e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SqueezeEffectInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture {
+ SqueezeEffectInteractor(
+ squeezeEffectRepository = fakeSqueezeEffectRepository
+ )
+ }
+
+ @Test
+ fun testIsSqueezeEffectDisabled_whenDisabledInRepository() =
+ kosmos.runTest {
+ fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = false
+
+ val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+ assertThat(isSqueezeEffectEnabled).isFalse()
+ }
+
+ @Test
+ fun testIsSqueezeEffectEnabled_whenEnabledInRepository() =
+ kosmos.runTest {
+ fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
+
+ val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+ assertThat(isSqueezeEffectEnabled).isTrue()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
index fdfcdc486c02..9a58365116fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
@@ -16,14 +16,13 @@
package com.android.systemui.util.wakelock
-import android.os.Build
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertLogsWtf
import org.junit.After
import org.junit.Assert
-import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -82,12 +81,11 @@ class ClientTrackingWakeLockTest : SysuiTestCase() {
@Test
fun wakeLock_releasedTooManyTimes_stillReleased_noThrow() {
- Assume.assumeFalse(Build.IS_ENG)
mWakeLock.acquire(WHY)
mWakeLock.acquire(WHY_2)
mWakeLock.release(WHY)
mWakeLock.release(WHY_2)
- mWakeLock.release(WHY)
+ assertLogsWtf { mWakeLock.release(WHY) }
Assert.assertFalse(mInner.isHeld)
}
@@ -104,9 +102,8 @@ class ClientTrackingWakeLockTest : SysuiTestCase() {
@Test
fun prodBuild_wakeLock_releaseWithoutAcquire_noThrow() {
- Assume.assumeFalse(Build.IS_ENG)
// shouldn't throw an exception on production builds
- mWakeLock.release(WHY)
+ assertLogsWtf { mWakeLock.release(WHY) }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index 90aecfb62e91..3951670c4125 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -19,16 +19,15 @@ package com.android.systemui.util.wakelock;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.os.Build;
import android.os.PowerManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogAssertKt;
import org.junit.After;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -93,8 +92,7 @@ public class WakeLockTest extends SysuiTestCase {
@Test
public void prodBuild_wakeLock_releaseWithoutAcquire_noThrow() {
- Assume.assumeFalse(Build.IS_ENG);
// shouldn't throw an exception on production builds
- mWakeLock.release(WHY);
+ LogAssertKt.assertRunnableLogsWtf(() -> mWakeLock.release(WHY));
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index cc6a7b93eef3..a0f1a5ce386e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
import android.app.WallpaperManager
+import android.app.WallpaperManager.FLAG_LOCK
import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
@@ -29,6 +30,7 @@ import com.android.app.wallpaperManager
import com.android.internal.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R as SysUIR
@@ -44,6 +46,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
@@ -64,6 +67,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val userRepository = kosmos.fakeUserRepository
private val broadcastDispatcher = kosmos.broadcastDispatcher
+ private val configRepository = kosmos.fakeConfigurationRepository
// Initialized in each test since certain flows rely on mocked data that isn't
// modifiable after start, like wallpaperManager.isWallpaperSupported
@@ -251,10 +255,18 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
secureSettings.putInt(Settings.Secure.DOZE_ALWAYS_ON_WALLPAPER_ENABLED, 1)
context.orCreateTestableResources.addOverride(
R.bool.config_dozeSupportsAodWallpaper,
- true,
+ false,
)
-
+ configRepository.onAnyConfigurationChange()
val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+ assertThat(latest).isFalse()
+
+ // Validate that a configuration change recalculates the flow
+ context.orCreateTestableResources.addOverride(
+ R.bool.config_dozeSupportsAodWallpaper,
+ true,
+ )
+ configRepository.onAnyConfigurationChange()
assertThat(latest).isTrue()
}
@@ -275,17 +287,17 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
@Test
@EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
- fun shouldSendNotificationLayout_setExtendedEffectsWallpaper_launchSendLayoutJob() =
+ fun shouldSendNotificationLayout_setExtendedEffectsWallpaper() =
testScope.runTest {
underTest = kosmos.wallpaperRepository
val latest by collectLastValue(underTest.shouldSendFocalArea)
- val extedendEffectsWallpaper =
+ val extendedEffectsWallpaper =
mock<WallpaperInfo>().apply {
whenever(this.component).thenReturn(ComponentName(context, focalAreaTarget))
}
whenever(kosmos.wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(extedendEffectsWallpaper)
+ .thenReturn(extendedEffectsWallpaper)
broadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -295,7 +307,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
@Test
@EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
- fun shouldSendNotificationLayout_setNotExtendedEffectsWallpaper_cancelSendLayoutJob() =
+ fun shouldSendNotificationLayout_setNotExtendedEffectsWallpaper() =
testScope.runTest {
underTest = kosmos.wallpaperRepository
val latest by collectLastValue(underTest.shouldSendFocalArea)
@@ -322,6 +334,51 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
assertThat(latest).isFalse()
}
+ @Test
+ @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+ fun shouldSendNotificationLayout_setExtendedEffectsWallpaperOnlyForHomescreen() =
+ testScope.runTest {
+ underTest = kosmos.wallpaperRepository
+ val latest by collectLastValue(underTest.shouldSendFocalArea)
+ val extendedEffectsWallpaper =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component).thenReturn(ComponentName("", focalAreaTarget))
+ }
+
+ whenever(kosmos.wallpaperManager.lockScreenWallpaperExists()).thenReturn(true)
+ whenever(kosmos.wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(extendedEffectsWallpaper)
+ whenever(kosmos.wallpaperManager.getWallpaperInfo(eq(FLAG_LOCK), any()))
+ .thenReturn(UNSUPPORTED_WP)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+ fun shouldSendNotificationLayout_setExtendedEffectsWallpaperOnlyForLockscreen() =
+ testScope.runTest {
+ underTest = kosmos.wallpaperRepository
+ val latest by collectLastValue(underTest.shouldSendFocalArea)
+ val extendedEffectsWallpaper =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component).thenReturn(ComponentName("", focalAreaTarget))
+ }
+ whenever(kosmos.wallpaperManager.lockScreenWallpaperExists()).thenReturn(true)
+ whenever(kosmos.wallpaperManager.getWallpaperInfoForUser(any()))
+ .thenReturn(UNSUPPORTED_WP)
+ whenever(kosmos.wallpaperManager.getWallpaperInfo(eq(FLAG_LOCK), any()))
+ .thenReturn(extendedEffectsWallpaper)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+ assertThat(latest).isTrue()
+ }
+
private companion object {
val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
val UNSUPPORTED_WP =
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
index 4c1f6450dd78..7657a2179d4f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
@@ -13,7 +13,6 @@
*/
package com.android.systemui.plugins.clocks
-import android.graphics.RectF
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.SimpleProperty
import java.io.PrintWriter
@@ -42,9 +41,13 @@ interface ClockController {
isDarkTheme: Boolean,
dozeFraction: Float,
foldFraction: Float,
- onBoundsChanged: (RectF) -> Unit,
+ clockListener: ClockEventListener?,
)
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter)
}
+
+interface ClockEventListener {
+ fun onBoundsChanged(bounds: VRectF)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt
index 235475f6b202..0fc3470716fe 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt
@@ -44,5 +44,5 @@ interface ClockEvents {
fun onZenDataChanged(data: ZenData)
/** Update reactive axes for this clock */
- fun onFontAxesChanged(axes: List<ClockFontAxisSetting>)
+ fun onFontAxesChanged(axes: ClockAxisStyle)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt
index 029e54658f60..a658c15a1a99 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt
@@ -13,6 +13,7 @@
*/
package com.android.systemui.plugins.clocks
+import android.content.Context
import android.graphics.Rect
import com.android.systemui.plugins.annotations.ProtectedInterface
@@ -44,6 +45,7 @@ interface ClockFaceEvents {
* render within the centered targetRect to avoid obstructing other elements. The specified
* targetRegion is relative to the parent view.
*/
+ @Deprecated("No longer necessary, pending removal")
fun onTargetRegionChanged(targetRegion: Rect?)
/** Called to notify the clock about its display. */
@@ -60,4 +62,12 @@ data class ThemeConfig(
* value denotes that we should use the seed color for the current system theme.
*/
val seedColor: Int?,
-)
+) {
+ fun getDefaultColor(context: Context): Int {
+ return when {
+ seedColor != null -> seedColor!!
+ isDarkTheme -> context.resources.getColor(android.R.color.system_accent1_100)
+ else -> context.resources.getColor(android.R.color.system_accent2_600)
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
index 02a3902a042c..5b67edda73cc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
@@ -16,7 +16,6 @@
package com.android.systemui.plugins.clocks
-import android.graphics.Rect
import android.view.View
import android.view.View.MeasureSpec
import com.android.systemui.log.core.LogLevel
@@ -56,12 +55,9 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- d({ "onLayout($bool1, ${Rect(int1, int2, long1.toInt(), long2.toInt())})" }) {
+ d({ "onLayout($bool1, ${VRect.fromLong(long1)})" }) {
bool1 = changed
- int1 = left
- int2 = top
- long1 = right.toLong()
- long2 = bottom.toLong()
+ long1 = VRect(left, top, right, bottom).toLong()
}
}
@@ -108,8 +104,11 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
- fun animateDoze() {
- d("animateDoze()")
+ fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
+ d({ "animateDoze(isDozing=$bool1, isAnimated=$bool2)" }) {
+ bool1 = isDozing
+ bool2 = isAnimated
+ }
}
fun animateCharge() {
@@ -117,10 +116,7 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
fun animateFidget(x: Float, y: Float) {
- d({ "animateFidget($str1, $str2)" }) {
- str1 = x.toString()
- str2 = y.toString()
- }
+ d({ "animateFidget(${VPointF.fromLong(long1)})" }) { long1 = VPointF(x, y).toLong() }
}
companion object {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
index 0cbc30d399d0..2147ca147b70 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
@@ -35,10 +35,54 @@ constructor(
/** Font axes that can be modified on this clock */
val axes: List<ClockFontAxis> = listOf(),
- /** List of font presets for this clock. Can be assigned directly. */
- val axisPresets: List<List<ClockFontAxisSetting>> = listOf(),
+ /** Presets for this clock. Null indicates the preset list should be disabled. */
+ val presetConfig: AxisPresetConfig? = null,
)
+data class AxisPresetConfig(
+ /** Groups of Presets. Each group can be used together in a single control. */
+ val groups: List<Group>,
+
+ /** Preset item currently being used, null when the current style is not a preset */
+ val current: IndexedStyle? = null,
+) {
+ /** The selected clock axis style, and its indices */
+ data class IndexedStyle(
+ /** Index of the group that this clock axis style appears in */
+ val groupIndex: Int,
+
+ /** Index of the preset within the group */
+ val presetIndex: Int,
+
+ /** Reference to the style in question */
+ val style: ClockAxisStyle,
+ )
+
+ /** A group of preset styles */
+ data class Group(
+ /* List of preset styles in this group */
+ val presets: List<ClockAxisStyle>,
+
+ /* Icon to use when this preset-group is active */
+ val icon: Drawable,
+ )
+
+ fun findStyle(style: ClockAxisStyle): IndexedStyle? {
+ groups.forEachIndexed { groupIndex, group ->
+ group.presets.forEachIndexed { presetIndex, preset ->
+ if (preset == style) {
+ return@findStyle IndexedStyle(
+ groupIndex = groupIndex,
+ presetIndex = presetIndex,
+ style = preset,
+ )
+ }
+ }
+ }
+ return null
+ }
+}
+
/** Represents an Axis that can be modified */
data class ClockFontAxis(
/** Axis key, not user renderable */
@@ -62,19 +106,12 @@ data class ClockFontAxis(
/** Description of the axis */
val description: String,
) {
- fun toSetting() = ClockFontAxisSetting(key, currentValue)
-
companion object {
- fun List<ClockFontAxis>.merge(
- axisSettings: List<ClockFontAxisSetting>
- ): List<ClockFontAxis> {
- val result = mutableListOf<ClockFontAxis>()
- for (axis in this) {
- val setting = axisSettings.firstOrNull { axis.key == it.key }
- val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
- result.add(output)
- }
- return result
+ fun List<ClockFontAxis>.merge(axisStyle: ClockAxisStyle): List<ClockFontAxis> {
+ return this.map { axis ->
+ axisStyle.get(axis.key)?.let { axis.copy(currentValue = it) } ?: axis
+ }
+ .toList()
}
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
index e7b36626a810..cccc55835302 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
@@ -22,7 +22,7 @@ import org.json.JSONObject
data class ClockSettings(
val clockId: ClockId? = null,
val seedColor: Int? = null,
- val axes: List<ClockFontAxisSetting> = listOf(),
+ val axes: ClockAxisStyle = ClockAxisStyle(),
) {
// Exclude metadata from equality checks
var metadata: JSONObject = JSONObject()
@@ -38,15 +38,15 @@ data class ClockSettings(
put(KEY_CLOCK_ID, setting.clockId)
put(KEY_SEED_COLOR, setting.seedColor)
put(KEY_METADATA, setting.metadata)
- put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
+ put(KEY_AXIS_LIST, ClockAxisStyle.toJson(setting.axes))
}
}
fun fromJson(json: JSONObject): ClockSettings {
val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
- val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
- return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
+ val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockAxisStyle::fromJson)
+ return ClockSettings(clockId, seedColor, axisList ?: ClockAxisStyle()).apply {
metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
}
}
@@ -54,64 +54,102 @@ data class ClockSettings(
}
@Keep
-/** Axis setting value for a clock */
-data class ClockFontAxisSetting(
- /** Axis key; matches ClockFontAxis.key */
- val key: String,
+class ClockAxisStyle {
+ private val settings: MutableMap<String, Float>
- /** Value to set this axis to */
- val value: Float,
-) {
- companion object {
- private val KEY_AXIS_KEY = "key"
- private val KEY_AXIS_VALUE = "value"
+ // Iterable would be implemented on ClockAxisStyle directly,
+ // but that doesn't appear to work with plugins/dynamic libs.
+ val items: Iterable<Map.Entry<String, Float>>
+ get() = settings.asIterable()
- fun toJson(setting: ClockFontAxisSetting): JSONObject {
- return JSONObject().apply {
- put(KEY_AXIS_KEY, setting.key)
- put(KEY_AXIS_VALUE, setting.value)
- }
- }
+ val isEmpty: Boolean
+ get() = settings.isEmpty()
- fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
- return JSONArray().apply {
- for (axis in settings) {
- put(toJson(axis))
- }
- }
+ constructor(initialize: ClockAxisStyle.() -> Unit = {}) {
+ settings = mutableMapOf()
+ this.initialize()
+ }
+
+ constructor(style: ClockAxisStyle) {
+ settings = style.settings.toMutableMap()
+ }
+
+ constructor(items: Map<String, Float>) {
+ settings = items.toMutableMap()
+ }
+
+ constructor(key: String, value: Float) {
+ settings = mutableMapOf(key to value)
+ }
+
+ constructor(items: List<ClockFontAxis>) {
+ settings = items.associate { it.key to it.currentValue }.toMutableMap()
+ }
+
+ fun copy(initialize: ClockAxisStyle.() -> Unit): ClockAxisStyle {
+ return ClockAxisStyle(this).apply { initialize() }
+ }
+
+ operator fun get(key: String): Float? = settings[key]
+
+ operator fun set(key: String, value: Float) = put(key, value)
+
+ fun put(key: String, value: Float) {
+ settings.put(key, value)
+ }
+
+ fun toFVar(): String {
+ val sb = StringBuilder()
+ for (axis in settings) {
+ if (sb.length > 0) sb.append(", ")
+ sb.append("'${axis.key}' ${axis.value.toInt()}")
}
+ return sb.toString()
+ }
- fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
- return ClockFontAxisSetting(
- key = jsonObj.getString(KEY_AXIS_KEY),
- value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
- )
+ fun copyWith(replacements: ClockAxisStyle): ClockAxisStyle {
+ val result = ClockAxisStyle(this)
+ for ((key, value) in replacements.settings) {
+ result[key] = value
}
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ClockAxisStyle) return false
+ return settings == other.settings
+ }
- fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
- val result = mutableListOf<ClockFontAxisSetting>()
+ companion object {
+ private val KEY_AXIS_KEY = "key"
+ private val KEY_AXIS_VALUE = "value"
+
+ fun fromJson(jsonArray: JSONArray): ClockAxisStyle {
+ val result = ClockAxisStyle()
for (i in 0..jsonArray.length() - 1) {
val obj = jsonArray.getJSONObject(i)
if (obj == null) continue
- result.add(fromJson(obj))
+
+ result.put(
+ key = obj.getString(KEY_AXIS_KEY),
+ value = obj.getDouble(KEY_AXIS_VALUE).toFloat(),
+ )
}
return result
}
- fun List<ClockFontAxisSetting>.toFVar(): String {
- val sb = StringBuilder()
- for (axis in this) {
- if (sb.length > 0) sb.append(", ")
- sb.append("'${axis.key}' ${axis.value.toInt()}")
+ fun toJson(style: ClockAxisStyle): JSONArray {
+ return JSONArray().apply {
+ for ((key, value) in style.settings) {
+ put(
+ JSONObject().apply {
+ put(KEY_AXIS_KEY, key)
+ put(KEY_AXIS_VALUE, value)
+ }
+ )
+ }
}
- return sb.toString()
- }
-
- fun List<ClockFontAxisSetting>.replace(
- replacements: List<ClockFontAxisSetting>
- ): List<ClockFontAxisSetting> {
- var remaining = this.filterNot { lhs -> replacements.any { rhs -> lhs.key == rhs.key } }
- return remaining + replacements
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt
index 3dae5305542b..de62f9c8be74 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.shared.clocks
+package com.android.systemui.plugins.clocks
import android.graphics.Point
import android.graphics.PointF
@@ -30,22 +30,20 @@ private val Y_MASK: ULong = 0x00000000FFFFFFFFU
private fun unpackX(data: ULong): Int = ((data and X_MASK) shr 32).toInt()
-private fun unpackY(data: ULong): Int = (data and Y_MASK).toInt()
+private fun unpackY(data: ULong): Int = ((data and Y_MASK) shr 0).toInt()
private fun pack(x: Int, y: Int): ULong {
- return ((x.toULong() shl 32) and X_MASK) or (y.toULong() and Y_MASK)
+ return ((x.toULong() shl 32) and X_MASK) or ((y.toULong() shl 0) and Y_MASK)
}
@JvmInline
-value class VPointF(private val data: ULong) {
+value class VPointF(val data: ULong) {
val x: Float
get() = Float.fromBits(unpackX(data))
val y: Float
get() = Float.fromBits(unpackY(data))
- constructor() : this(0f, 0f)
-
constructor(pt: PointF) : this(pt.x, pt.y)
constructor(x: Int, y: Int) : this(x.toFloat(), y.toFloat())
@@ -58,6 +56,8 @@ value class VPointF(private val data: ULong) {
fun toPointF() = PointF(x, y)
+ fun toLong(): Long = data.toLong()
+
fun lengthSq(): Float = x * x + y * y
fun length(): Float = sqrt(lengthSq())
@@ -112,6 +112,8 @@ value class VPointF(private val data: ULong) {
companion object {
val ZERO = VPointF(0, 0)
+ fun fromLong(data: Long) = VPointF(data.toULong())
+
fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y))
fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y))
@@ -139,19 +141,19 @@ value class VPointF(private val data: ULong) {
}
@JvmInline
-value class VPoint(private val data: ULong) {
+value class VPoint(val data: ULong) {
val x: Int
get() = unpackX(data)
val y: Int
get() = unpackY(data)
- constructor() : this(0, 0)
-
constructor(x: Int, y: Int) : this(pack(x, y))
fun toPoint() = Point(x, y)
+ fun toLong(): Long = data.toLong()
+
fun abs() = VPoint(abs(x), abs(y))
operator fun component1(): Int = x
@@ -195,6 +197,8 @@ value class VPoint(private val data: ULong) {
companion object {
val ZERO = VPoint(0, 0)
+ fun fromLong(data: Long) = VPoint(data.toULong())
+
fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y))
fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y))
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt
new file mode 100644
index 000000000000..1bd29aa6a073
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins.clocks
+
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.Half
+
+private val LEFT_MASK: ULong = 0xFFFF000000000000U
+private val TOP_MASK: ULong = 0x0000FFFF00000000U
+private val RIGHT_MASK: ULong = 0x00000000FFFF0000U
+private val BOTTOM_MASK: ULong = 0x000000000000FFFFU
+
+private fun unpackLeft(data: ULong): Short = ((data and LEFT_MASK) shr 48).toShort()
+
+private fun unpackTop(data: ULong): Short = ((data and TOP_MASK) shr 32).toShort()
+
+private fun unpackRight(data: ULong): Short = ((data and RIGHT_MASK) shr 16).toShort()
+
+private fun unpackBottom(data: ULong): Short = ((data and BOTTOM_MASK) shr 0).toShort()
+
+private fun pack(left: Short, top: Short, right: Short, bottom: Short): ULong {
+ return ((left.toULong() shl 48) and LEFT_MASK) or
+ ((top.toULong() shl 32) and TOP_MASK) or
+ ((right.toULong() shl 16) and RIGHT_MASK) or
+ ((bottom.toULong() shl 0) and BOTTOM_MASK)
+}
+
+@JvmInline
+value class VRectF(val data: ULong) {
+ val left: Float
+ get() = fromBits(unpackLeft(data))
+
+ val top: Float
+ get() = fromBits(unpackTop(data))
+
+ val right: Float
+ get() = fromBits(unpackRight(data))
+
+ val bottom: Float
+ get() = fromBits(unpackBottom(data))
+
+ val width: Float
+ get() = right - left
+
+ val height: Float
+ get() = bottom - top
+
+ constructor(rect: RectF) : this(rect.left, rect.top, rect.right, rect.bottom)
+
+ constructor(
+ rect: Rect
+ ) : this(
+ left = rect.left.toFloat(),
+ top = rect.top.toFloat(),
+ right = rect.right.toFloat(),
+ bottom = rect.bottom.toFloat(),
+ )
+
+ constructor(
+ left: Float,
+ top: Float,
+ right: Float,
+ bottom: Float,
+ ) : this(pack(toBits(left), toBits(top), toBits(right), toBits(bottom)))
+
+ val center: VPointF
+ get() = VPointF(left, top) + size / 2f
+
+ val size: VPointF
+ get() = VPointF(width, height)
+
+ fun toRectF(): RectF = RectF(left, top, right, bottom)
+
+ fun toLong(): Long = data.toLong()
+
+ override fun toString() = "($left, $top) -> ($right, $bottom)"
+
+ companion object {
+ private fun toBits(value: Float): Short = Half.halfToShortBits(Half.toHalf(value))
+
+ private fun fromBits(value: Short): Float = Half.toFloat(Half.intBitsToHalf(value.toInt()))
+
+ fun fromLong(data: Long) = VRectF(data.toULong())
+
+ fun fromCenter(center: VPointF, size: VPointF): VRectF {
+ return VRectF(
+ center.x - size.x / 2,
+ center.y - size.y / 2,
+ center.x + size.x / 2,
+ center.y + size.y / 2,
+ )
+ }
+
+ fun fromTopLeft(pos: VPointF, size: VPointF): VRectF {
+ return VRectF(pos.x, pos.y, pos.x + size.x, pos.y + size.y)
+ }
+
+ val ZERO = VRectF(0f, 0f, 0f, 0f)
+ }
+}
+
+@JvmInline
+value class VRect(val data: ULong) {
+ val left: Int
+ get() = unpackLeft(data).toInt()
+
+ val top: Int
+ get() = unpackTop(data).toInt()
+
+ val right: Int
+ get() = unpackRight(data).toInt()
+
+ val bottom: Int
+ get() = unpackBottom(data).toInt()
+
+ val width: Int
+ get() = right - left
+
+ val height: Int
+ get() = bottom - top
+
+ constructor(
+ rect: Rect
+ ) : this(
+ left = rect.left.toShort(),
+ top = rect.top.toShort(),
+ right = rect.right.toShort(),
+ bottom = rect.bottom.toShort(),
+ )
+
+ constructor(
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ ) : this(
+ left = left.toShort(),
+ top = top.toShort(),
+ right = right.toShort(),
+ bottom = bottom.toShort(),
+ )
+
+ constructor(
+ left: Short,
+ top: Short,
+ right: Short,
+ bottom: Short,
+ ) : this(pack(left, top, right, bottom))
+
+ val center: VPoint
+ get() = VPoint(left, top) + size / 2
+
+ val size: VPoint
+ get() = VPoint(width, height)
+
+ fun toRect(): Rect = Rect(left, top, right, bottom)
+
+ fun toLong(): Long = data.toLong()
+
+ override fun toString() = "($left, $top) -> ($right, $bottom)"
+
+ companion object {
+ val ZERO = VRect(0, 0, 0, 0)
+
+ fun fromLong(data: Long) = VRect(data.toULong())
+
+ fun fromCenter(center: VPoint, size: VPoint): VRect {
+ return VRect(
+ (center.x - size.x / 2).toShort(),
+ (center.y - size.y / 2).toShort(),
+ (center.x + size.x / 2).toShort(),
+ (center.y + size.y / 2).toShort(),
+ )
+ }
+
+ fun fromTopLeft(pos: VPoint, size: VPoint): VRect {
+ return VRect(
+ pos.x.toShort(),
+ pos.y.toShort(),
+ (pos.x + size.x).toShort(),
+ (pos.y + size.y).toShort(),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index f920b187e7e5..f59dda049aa1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -24,6 +24,8 @@ data class WeatherData(
@VisibleForTesting const val TEMPERATURE_KEY = "temperature"
private const val INVALID_WEATHER_ICON_STATE = -1
+ @JvmStatic
+ @JvmOverloads
fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? {
val description = extras.getString(DESCRIPTION_KEY)
val state =
@@ -46,7 +48,7 @@ data class WeatherData(
state = state,
useCelsius = extras.getBoolean(USE_CELSIUS_KEY),
temperature = temperature,
- touchAction = touchAction
+ touchAction = touchAction,
)
if (DEBUG) {
Log.i(TAG, "Weather data parsed $result from $extras")
@@ -87,53 +89,53 @@ data class WeatherData(
}
// Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
- enum class WeatherStateIcon(val id: Int) {
- UNKNOWN_ICON(0),
+ enum class WeatherStateIcon(val id: Int, val icon: String) {
+ UNKNOWN_ICON(0, ""),
// Clear, day & night.
- SUNNY(1),
- CLEAR_NIGHT(2),
+ SUNNY(1, "a"),
+ CLEAR_NIGHT(2, "f"),
// Mostly clear, day & night.
- MOSTLY_SUNNY(3),
- MOSTLY_CLEAR_NIGHT(4),
+ MOSTLY_SUNNY(3, "b"),
+ MOSTLY_CLEAR_NIGHT(4, "n"),
// Partly cloudy, day & night.
- PARTLY_CLOUDY(5),
- PARTLY_CLOUDY_NIGHT(6),
+ PARTLY_CLOUDY(5, "b"),
+ PARTLY_CLOUDY_NIGHT(6, "n"),
// Mostly cloudy, day & night.
- MOSTLY_CLOUDY_DAY(7),
- MOSTLY_CLOUDY_NIGHT(8),
- CLOUDY(9),
- HAZE_FOG_DUST_SMOKE(10),
- DRIZZLE(11),
- HEAVY_RAIN(12),
- SHOWERS_RAIN(13),
+ MOSTLY_CLOUDY_DAY(7, "e"),
+ MOSTLY_CLOUDY_NIGHT(8, "e"),
+ CLOUDY(9, "e"),
+ HAZE_FOG_DUST_SMOKE(10, "d"),
+ DRIZZLE(11, "c"),
+ HEAVY_RAIN(12, "c"),
+ SHOWERS_RAIN(13, "c"),
// Scattered showers, day & night.
- SCATTERED_SHOWERS_DAY(14),
- SCATTERED_SHOWERS_NIGHT(15),
+ SCATTERED_SHOWERS_DAY(14, "c"),
+ SCATTERED_SHOWERS_NIGHT(15, "c"),
// Isolated scattered thunderstorms, day & night.
- ISOLATED_SCATTERED_TSTORMS_DAY(16),
- ISOLATED_SCATTERED_TSTORMS_NIGHT(17),
- STRONG_TSTORMS(18),
- BLIZZARD(19),
- BLOWING_SNOW(20),
- FLURRIES(21),
- HEAVY_SNOW(22),
+ ISOLATED_SCATTERED_TSTORMS_DAY(16, "i"),
+ ISOLATED_SCATTERED_TSTORMS_NIGHT(17, "i"),
+ STRONG_TSTORMS(18, "i"),
+ BLIZZARD(19, "j"),
+ BLOWING_SNOW(20, "j"),
+ FLURRIES(21, "h"),
+ HEAVY_SNOW(22, "j"),
// Scattered snow showers, day & night.
- SCATTERED_SNOW_SHOWERS_DAY(23),
- SCATTERED_SNOW_SHOWERS_NIGHT(24),
- SNOW_SHOWERS_SNOW(25),
- MIXED_RAIN_HAIL_RAIN_SLEET(26),
- SLEET_HAIL(27),
- TORNADO(28),
- TROPICAL_STORM_HURRICANE(29),
- WINDY_BREEZY(30),
- WINTRY_MIX_RAIN_SNOW(31);
+ SCATTERED_SNOW_SHOWERS_DAY(23, "h"),
+ SCATTERED_SNOW_SHOWERS_NIGHT(24, "h"),
+ SNOW_SHOWERS_SNOW(25, "g"),
+ MIXED_RAIN_HAIL_RAIN_SLEET(26, "h"),
+ SLEET_HAIL(27, "h"),
+ TORNADO(28, "l"),
+ TROPICAL_STORM_HURRICANE(29, "m"),
+ WINDY_BREEZY(30, "k"),
+ WINTRY_MIX_RAIN_SNOW(31, "h");
companion object {
fun fromInt(value: Int) = values().firstOrNull { it.id == value }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
index be0362fd7481..ac7a85742385 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
@@ -16,15 +16,13 @@
package com.android.systemui.plugins.qs
-/**
- * The base view model class for rendering the Tile's TileDetailsView.
- */
-abstract class TileDetailsViewModel {
+/** The view model interface for rendering the Tile's TileDetailsView. */
+interface TileDetailsViewModel {
// The callback when the settings button is clicked. Currently this is the same as the on tile
// long press callback
- abstract fun clickOnSettingsButton()
+ fun clickOnSettingsButton()
- abstract fun getTitle(): String
+ val title: String
- abstract fun getSubTitle(): String
+ val subTitle: String
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt
index 3e39ae9a3fe5..0c46e0784a31 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplaySpecific.kt
@@ -18,4 +18,7 @@ package com.android.systemui.dagger.qualifiers
import javax.inject.Qualifier
/** Annotates a class that is display specific. */
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class DisplaySpecific
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+public annotation class DisplaySpecific
diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Main.java
index 7b097740ff11..7b097740ff11 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Main.java
diff --git a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/UiBackground.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/UiBackground.java
index 3d37468c322a..3d37468c322a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/UiBackground.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/UiBackground.java
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 1a49bbb27c8f..0524b34e6b2e 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -21,11 +21,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"‏أدخل رقم التعريف الشخصي (PIN)"</string>
- <string name="keyguard_enter_pin" msgid="8114529922480276834">"أدخِل رقم التعريف الشخصي"</string>
+ <string name="keyguard_enter_pin" msgid="8114529922480276834">"يُرجى إدخال رقم التعريف الشخصي"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"أدخل النقش"</string>
<string name="keyguard_enter_pattern" msgid="7616595160901084119">"ارسم النقش"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"أدخل كلمة المرور"</string>
- <string name="keyguard_enter_password" msgid="6483623792371009758">"أدخِل كلمة المرور"</string>
+ <string name="keyguard_enter_password" msgid="6483623792371009758">"يُرجى إدخال كلمة المرور"</string>
<string name="keyguard_sim_error_message_short" msgid="633630844240494070">"بطاقة غير صالحة."</string>
<string name="keyguard_charged" msgid="5478247181205188995">"اكتمل الشحن"</string>
<string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن لاسلكيًا"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 9eb89b67fdde..cd63405f1c00 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -23,7 +23,7 @@
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"अपना पिन डालें"</string>
<string name="keyguard_enter_pin" msgid="8114529922480276834">"पिन डालें"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"अपना पैटर्न डालें"</string>
- <string name="keyguard_enter_pattern" msgid="7616595160901084119">"पैटर्न ड्रॉ करें"</string>
+ <string name="keyguard_enter_pattern" msgid="7616595160901084119">"पैटर्न बनाएं"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"अपना पासवर्ड डालें"</string>
<string name="keyguard_enter_password" msgid="6483623792371009758">"पासवर्ड डालें"</string>
<string name="keyguard_sim_error_message_short" msgid="633630844240494070">"गलत कार्ड."</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index f014c298e2c6..67ee3ed1b99d 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -23,7 +23,7 @@
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"מה קוד האימות שלך?"</string>
<string name="keyguard_enter_pin" msgid="8114529922480276834">"צריך להזין קוד אימות"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"יש להזין קו ביטול נעילה"</string>
- <string name="keyguard_enter_pattern" msgid="7616595160901084119">"צריך לצייר קו ביטול נעילה"</string>
+ <string name="keyguard_enter_pattern" msgid="7616595160901084119">"צריך לצייר את קו פתיחת הנעילה"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"יש להזין סיסמה"</string>
<string name="keyguard_enter_password" msgid="6483623792371009758">"צריך להזין סיסמה"</string>
<string name="keyguard_sim_error_message_short" msgid="633630844240494070">"כרטיס לא חוקי."</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 6b08a3a420cc..dbfc4940614a 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -62,7 +62,7 @@
<string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"请重试,或输入 PIN 码"</string>
<string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"请重试,或输入密码"</string>
<string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"请重试,或绘制解锁图案"</string>
- <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"如果出错的尝试次数太多,必须输入 PIN 码才能解锁"</string>
+ <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"如果错误次数太多,则必须输入 PIN 码才能解锁"</string>
<string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"如果多次尝试失败,必须输入密码才能解锁"</string>
<string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"如果出错的尝试次数太多,必须绘制图案才能解锁"</string>
<string name="kg_unlock_with_pin_or_fp" msgid="5635161174698729890">"请使用 PIN 码或指纹解锁"</string>
diff --git a/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml b/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml
new file mode 100644
index 000000000000..84e228e436cb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/notification_ripple_untinted_color">
+ <item>
+ <inset
+ android:insetLeft="0dp"
+ android:insetTop="6dp"
+ android:insetRight="0dp"
+ android:insetBottom="6dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/notification_2025_smart_reply_button_corner_radius" />
+ <stroke android:width="@dimen/smart_reply_button_stroke_width"
+ android:color="@color/smart_reply_button_stroke" />
+ <solid android:color="@color/smart_reply_button_background"/>
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 124aec6a92dd..da2ec43e470c 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -52,7 +52,7 @@
android:gravity="center_vertical"
android:textSize="14sp" />
- <TextView
+ <com.android.systemui.util.DelayableMarqueeTextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/bluetooth_device_summary"
@@ -60,7 +60,9 @@
android:paddingEnd="10dp"
android:paddingBottom="15dp"
android:maxLines="1"
- android:ellipsize="end"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
+ android:singleLine="true"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
app:layout_constraintEnd_toStartOf="@+id/guideline"
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 915563b1ae20..c7add163dffa 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -85,6 +85,7 @@
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:scrollbars="none"
+ android:importantForAccessibility="no"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
app:layout_constraintWidth_max="wrap"
@@ -176,6 +177,8 @@
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="4dp"
android:layout_marginBottom="2dp"
+ android:importantForAccessibility="yes"
+ android:contentDescription="@string/clipboard_overlay_window_name"
android:background="@drawable/clipboard_minimized_background_inset">
<ImageView
android:src="@drawable/ic_content_paste"
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index 949a6abb9b9d..a1b26fc9bb40 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -85,13 +85,49 @@
android:longClickable="false"/>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/input_routing_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/preset_layout"
+ android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+ android:orientation="vertical"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/input_routing_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+ android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+ android:text="@string/hearing_devices_input_routing_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textSize="14sp"
+ android:gravity="center_vertical"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textDirection="locale"/>
+ <Spinner
+ android:id="@+id/input_routing_spinner"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:layout_marginTop="4dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:background="@drawable/hearing_devices_spinner_background"
+ android:popupBackground="@drawable/hearing_devices_spinner_popup_background"
+ android:dropDownWidth="match_parent"
+ android:longClickable="false"/>
+ </LinearLayout>
+
<com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout
android:id="@+id/ambient_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/preset_layout"
+ app:layout_constraintTop_toBottomOf="@id/input_routing_layout"
android:layout_marginTop="@dimen/hearing_devices_layout_margin" />
<LinearLayout
diff --git a/packages/SystemUI/res/layout/magic_action_button.xml b/packages/SystemUI/res/layout/magic_action_button.xml
index 381d6b17dec5..63fc1e485635 100644
--- a/packages/SystemUI/res/layout/magic_action_button.xml
+++ b/packages/SystemUI/res/layout/magic_action_button.xml
@@ -6,7 +6,7 @@
android:background="@drawable/magic_action_button_background"
android:drawablePadding="@dimen/magic_action_button_drawable_padding"
android:ellipsize="none"
- android:fontFamily="google-sans-flex"
+ android:fontFamily="google-sans-flex-medium"
android:gravity="center"
android:minWidth="0dp"
android:paddingHorizontal="@dimen/magic_action_button_padding_horizontal"
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 6f8b4cd3e4a9..9b629ace76af 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -15,8 +15,8 @@
~ limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/media_output_dialog"
android:layout_width="@dimen/large_dialog_width"
android:layout_height="wrap_content"
@@ -35,24 +35,25 @@
android:orientation="horizontal">
<ImageView
android:id="@+id/header_icon"
- android:layout_width="72dp"
- android:layout_height="72dp"
+ android:layout_width="@dimen/media_output_dialog_header_album_icon_size"
+ android:layout_height="@dimen/media_output_dialog_header_album_icon_size"
+ android:layout_marginEnd="@dimen/media_output_dialog_header_icon_padding"
android:importantForAccessibility="no"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="12dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
+ android:gravity="top"
android:orientation="horizontal">
<ImageView
android:id="@+id/app_source_icon"
android:layout_width="20dp"
android:layout_height="20dp"
+ android:layout_marginBottom="7dp"
android:gravity="center_vertical"
android:importantForAccessibility="no"/>
@@ -103,12 +104,11 @@
android:layout_height="wrap_content" >
</ViewStub>
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/device_list"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical">
+ android:layout_height="0dp"
+ android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_result"
@@ -116,8 +116,11 @@
android:paddingTop="8dp"
android:clipToPadding="false"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- </LinearLayout>
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHeight_max="@dimen/media_output_dialog_list_max_height"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
deleted file mode 100644
index e63aa211f9f1..000000000000
--- a/packages/SystemUI/res/layout/media_recommendation_view.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<!-- Layout for media recommendation item inside QSPanel carousel -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Album cover -->
- <ImageView
- android:id="@+id/media_cover"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:translationZ="0dp"
- android:scaleType="matrix"
- android:adjustViewBounds="true"
- android:clipToOutline="true"
- android:layerType="hardware"
- android:background="@drawable/bg_smartspace_media_item"/>
-
- <!-- App icon -->
- <com.android.internal.widget.CachingIconView
- android:id="@+id/media_rec_app_icon"
- android:layout_width="@dimen/qs_media_rec_album_icon_size"
- android:layout_height="@dimen/qs_media_rec_album_icon_size"
- android:minWidth="@dimen/qs_media_rec_album_icon_size"
- android:minHeight="@dimen/qs_media_rec_album_icon_size"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginTop="@dimen/qs_media_info_spacing"/>
-
- <!-- Artist name -->
- <TextView
- android:id="@+id/media_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:singleLine="true"
- android:textSize="12sp"
- android:gravity="top"
- android:layout_gravity="bottom"
- android:importantForAccessibility="no"/>
-
- <!-- Album name -->
- <TextView
- android:id="@+id/media_subtitle"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginBottom="@dimen/qs_media_info_spacing"
- android:fontFamily="@*android:string/config_headlineFontFamily"
- android:singleLine="true"
- android:textSize="11sp"
- android:gravity="center_vertical"
- android:layout_gravity="bottom"
- android:importantForAccessibility="no"/>
-
- <!-- Seek Bar -->
- <SeekBar
- android:id="@+id/media_progress_bar"
- android:layout_width="match_parent"
- android:layout_height="12dp"
- android:layout_gravity="bottom"
- android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
- android:thumb="@android:color/transparent"
- android:splitTrack="false"
- android:clickable="false"
- android:progressTint="?android:attr/textColorPrimary"
- android:progressBackgroundTint="?android:attr/textColorTertiary"
- android:paddingTop="5dp"
- android:paddingBottom="5dp"
- android:paddingStart="0dp"
- android:paddingEnd="0dp"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginBottom="@dimen/qs_media_info_spacing"/>
-</merge> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml
deleted file mode 100644
index 65fc19c5b2a4..000000000000
--- a/packages/SystemUI/res/layout/media_recommendations.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<com.android.systemui.util.animation.TransitionLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/media_recommendations_updated"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:forceHasOverlappingRendering="false"
- android:background="@drawable/qs_media_background"
- android:theme="@style/MediaPlayer">
-
- <!-- This view just ensures the full media player is a certain height. -->
- <View
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded" />
-
- <TextView
- android:id="@+id/media_rec_title"
- style="@style/MediaPlayer.Recommendation.Header"
- android:text="@string/controls_media_smartspace_rec_header"/>
-
- <FrameLayout
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- >
-
- <include
- layout="@layout/media_recommendation_view"/>
-
- </FrameLayout>
-
-
- <FrameLayout
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- >
-
- <include
- layout="@layout/media_recommendation_view"/>
-
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- >
-
- <include
- layout="@layout/media_recommendation_view"/>
-
- </FrameLayout>
-
- <include
- layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 9a66ca9f3baa..603cc00a312c 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -71,15 +71,18 @@
android:textSize="16sp"
/>
- <Switch
- android:id="@+id/toggle"
+ <com.google.android.material.materialswitch.MaterialSwitch
+ android:theme="@style/Theme.Material3.DynamicColors.DayNight"
+ android:id="@+id/material_toggle"
+ android:filterTouchesWhenObscured="false"
+ android:clickable="true"
+ android:focusable="true"
+ android:padding="8dp"
android:layout_height="48dp"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
- android:padding="8dp"
- android:track="@drawable/settingslib_track_selector"
- android:thumb="@drawable/settingslib_thumb_selector"
- android:theme="@style/MainSwitch.Settingslib"/>
+ style="@style/SettingslibSwitchStyle.Expressive"/>
+
</com.android.systemui.statusbar.notification.row.AppControlView>
<ScrollView
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index b2eaa6ce92b5..4bc37f82a9d7 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -80,15 +80,16 @@
/>
</RelativeLayout>
- <Switch
- android:id="@+id/toggle"
+ <com.google.android.material.materialswitch.MaterialSwitch
+ android:theme="@style/Theme.Material3.DynamicColors.DayNight"
+ android:id="@+id/material_toggle"
+ android:filterTouchesWhenObscured="false"
+ android:clickable="true"
+ android:focusable="true"
+ android:padding="8dp"
android:layout_height="48dp"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
- android:padding="8dp"
- android:track="@drawable/settingslib_track_selector"
- android:thumb="@drawable/settingslib_thumb_selector"
- android:theme="@style/MainSwitch.Settingslib"
- />
+ style="@style/SettingslibSwitchStyle.Expressive"/>
</LinearLayout>
</com.android.systemui.statusbar.notification.row.ChannelRow>
diff --git a/packages/SystemUI/res/layout/notification_2025_info.xml b/packages/SystemUI/res/layout/notification_2025_info.xml
index 7b6916652924..fa852a2b8e85 100644
--- a/packages/SystemUI/res/layout/notification_2025_info.xml
+++ b/packages/SystemUI/res/layout/notification_2025_info.xml
@@ -18,6 +18,7 @@
<!-- extends LinearLayout -->
<com.android.systemui.statusbar.notification.row.NotificationInfo
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/notification_guts"
android:layout_width="match_parent"
@@ -324,18 +325,34 @@
</com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
</LinearLayout>
-
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bottom_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@*android:dimen/notification_2025_margin"
android:minHeight="@dimen/notification_2025_guts_button_size"
- android:gravity="center_vertical"
- >
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="@*android:dimen/notification_2025_margin"
+ app:layout_constraintStart_toStartOf="parent"
+ android:gravity="center"
+ android:minWidth="@dimen/notification_2025_min_tap_target_size"
+ android:minHeight="@dimen/notification_2025_min_tap_target_size"
+ android:maxWidth="200dp"
+ style="@style/TextAppearance.NotificationInfo.Button"
+ android:textSize="@*android:dimen/notification_2025_action_text_size"
+ />
<TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
+ android:paddingStart="@dimen/notification_importance_button_padding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
@@ -345,6 +362,8 @@
android:minWidth="@dimen/notification_2025_min_tap_target_size"
android:minHeight="@dimen/notification_2025_min_tap_target_size"
android:maxWidth="200dp"
+ app:layout_constraintStart_toEndOf="@id/inline_dismiss"
+ app:layout_constraintBaseline_toBaselineOf="@id/inline_dismiss"
style="@style/TextAppearance.NotificationInfo.Button"
android:textSize="@*android:dimen/notification_2025_action_text_size"/>
<TextView
@@ -354,12 +373,18 @@
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="@*android:dimen/notification_2025_margin"
- android:gravity="center"
+ android:gravity="end"
+ app:layout_constraintEnd_toEndOf="parent"
android:minWidth="@dimen/notification_2025_min_tap_target_size"
android:minHeight="@dimen/notification_2025_min_tap_target_size"
android:maxWidth="125dp"
style="@style/TextAppearance.NotificationInfo.Button"
android:textSize="@*android:dimen/notification_2025_action_text_size"/>
- </LinearLayout>
+ <androidx.constraintlayout.helper.widget.Flow
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:constraint_referenced_ids="inline_dismiss,turn_off_notifications,done"
+ app:flow_wrapMode="chain"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</com.android.systemui.statusbar.notification.row.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml b/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml
new file mode 100644
index 000000000000..ed905885a76f
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- android:paddingHorizontal is set dynamically in SmartReplyView. -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@android:style/Widget.Material.Button"
+ android:stateListAnimator="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="0dp"
+ android:minHeight="@dimen/notification_2025_smart_reply_button_min_height"
+ android:paddingVertical="@dimen/smart_reply_button_padding_vertical"
+ android:background="@drawable/notification_2025_smart_reply_button_background"
+ android:gravity="center"
+ android:fontFamily="google-sans-flex"
+ android:textSize="@dimen/smart_reply_button_font_size"
+ android:textColor="@color/smart_reply_button_text"
+ android:paddingStart="@dimen/smart_reply_button_action_padding_left"
+ android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
+ android:drawablePadding="@dimen/smart_action_button_icon_padding"
+ android:textStyle="normal"
+ android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml b/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml
new file mode 100644
index 000000000000..4f543e5099bf
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- android:paddingHorizontal is set dynamically in SmartReplyView. -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@android:style/Widget.Material.Button"
+ android:stateListAnimator="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="0dp"
+ android:minHeight="@dimen/notification_2025_smart_reply_button_min_height"
+ android:paddingVertical="@dimen/smart_reply_button_padding_vertical"
+ android:background="@drawable/notification_2025_smart_reply_button_background"
+ android:gravity="center"
+ android:fontFamily="google-sans-flex"
+ android:textSize="@dimen/smart_reply_button_font_size"
+ android:textColor="@color/smart_reply_button_text"
+ android:paddingStart="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
+ android:textStyle="normal"
+ android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 089ceaee6ce3..d4bd142d9089 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -341,15 +341,28 @@ asked for it -->
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
+ android:layout_toEndOf="@id/inline_dismiss"
android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
+ android:paddingStart="@dimen/notification_importance_button_padding"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 4850b35833e5..d1755eff6dab 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -143,15 +143,28 @@
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
+ android:layout_toEndOf="@id/inline_dismiss"
android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
+ android:paddingStart="@dimen/notification_importance_button_padding"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml
index 2e0a0ca1185c..3982a6638666 100644
--- a/packages/SystemUI/res/layout/promoted_notification_info.xml
+++ b/packages/SystemUI/res/layout/promoted_notification_info.xml
@@ -373,15 +373,28 @@ asked for it -->
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
+ android:layout_toEndOf="@id/inline_dismiss"
android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
+ android:paddingStart="@dimen/notification_importance_button_padding"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index e80ed26cffe5..22599a35ed64 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -20,6 +20,7 @@
android:id="@+id/sidefps_animation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:contentDescription="@string/accessibility_side_fingerprint_indicator_label"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/sfps_pulse"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f41eaec8e18b..a1fa54cf592d 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -40,7 +40,7 @@
android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
android:clipChildren="false"
app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
- app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_background"
app:layout_constraintHeight_default="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -70,9 +70,9 @@
android:layout_marginTop="@dimen/volume_dialog_components_spacing"
android:clipChildren="false"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_background"
app:layout_constraintHeight_default="wrap"
- app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_background"
app:layout_constraintTop_toBottomOf="@id/volume_dialog_main_slider_container"
app:layout_constraintVertical_bias="0"
app:layout_constraintWidth_default="wrap">
diff --git a/packages/SystemUI/res/layout/volume_dialog_top_section.xml b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
index 29f52480bfe0..b7455471d9a6 100644
--- a/packages/SystemUI/res/layout/volume_dialog_top_section.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
@@ -22,7 +22,7 @@
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center"
- android:paddingEnd="@dimen/volume_dialog_ringer_drawer_diff_end_margin"
+ android:paddingEnd="@dimen/volume_dialog_buttons_margin"
app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene">
<View
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
index b1d6a270bc67..3f03fcff7603 100644
--- a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
@@ -1 +1 @@
-{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
+{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimary","cl":"onPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimary","cl":"onPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimary","cl":"onPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index d8e08fc1aec3..62363358f663 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Gekoppel aan <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Gekoppel aan <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Vou groep uit."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Voeg toestel by groep."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Verwyder toestel uit groep."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Maak app oop."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nie gekoppel nie."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Swerwing"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Gehoortoestelle"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Skakel tans aan …"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kan nie helderheid verstel nie omdat dit deur die boonste app beheer word"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Outodraai"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Outodraai skerm"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Ligging"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgewing"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Links"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Regs"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Vou uit na links- en regsgeskeide kontroles"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Vou in na verenigde kontrole"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Demp omgewing"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ontdemp omgewing"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Nutsgoed"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Intydse Onderskrifte"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblokkeer toestelmikrofoon?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblokkeer toestelkamera?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Gebruik verdeelde skerm met app aan die regterkant"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Gebruik verdeelde skerm met app aan die linkerkant"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Gebruik volskerm"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Gebruik rekenaaraansig"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Skakel oor na app regs of onder terwyl jy verdeelde skerm gebruik"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skakel oor na app links of bo terwyl jy verdeelde skerm gebruik"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Tydens verdeelde skerm: verplaas ’n app van een skerm na ’n ander"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Wys laeprioriteit-kennisgewingikone"</string>
<string name="other" msgid="429768510980739978">"Ander"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"wissel die teël se grootte"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"verwyder teël"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"voeg teël by die laaste posisie"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Skuif teël"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Voeg by posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisie is ongeldig."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Teël is bygevoeg"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Teël is verwyder"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kitsinstellingswysiger."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Swiep op en hou met drie vingers op die raakpaneel om onlangse apps te bekyk"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Druk die handelingsleutel op jou sleutelbord om al jou apps te bekyk"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Gewysig"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Ontsluit om te kyk"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Ontsluit om kode te kyk"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstuele opvoeding"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Gebruik jou raakpaneel om terug te gaan"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swiep links of regs met drie vingers. Tik om meer gebare te leer."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Onbekend"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Stel alle teëls terug?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle Kitsinstellingsteëls sal na die toestel se oorspronklike instellings teruggestel word"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index ea51f0003be6..e180d114a75a 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ከ<xliff:g id="BLUETOOTH">%s</xliff:g> ጋር ተገናኝቷል።"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"ከ<xliff:g id="CAST">%s</xliff:g> ጋር ተገናኝቷል።"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ቡድንን ዘርጋ።"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"መሣሪያን ወደ ቡድን አክል።"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"መሣሪያን ከቡድን ላይ አስወግድ።"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"መተግበሪያ ክፈት።"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"አልተገናኘም።"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"በማዛወር ላይ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ግቤት"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"መስሚያ አጋዥ መሣሪያዎች"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"በማብራት ላይ..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ከላይ ባለው መተግበሪያ ቁጥጥር እየተደረገበት ስለሆነ ብሩህነትን ማስተካከል አልተቻለም"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"በራስ ሰር አሽከርክር"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ማያ ገጽን በራስ-አሽከርክር"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"አካባቢ"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"በዙሪያ ያሉ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ግራ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ቀኝ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ወደ ግራ እና ቀኝ የተለያዩ ቁጥጥሮች ዘርጋ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ወደ የተዋሃደ ቁጥጥር ሰብስብ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"በዙሪያ ያሉትን ድምፀ-ከል አድርግ"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"በዙሪያ ያሉትን ድምፅ-ከል አንሳ"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"መሣሪያዎች"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"የቀጥታ መግለጫ ጽሑፍ"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ማስታወሻ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"የመሣሪያ ማይክሮፎን እገዳ ይነሳ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"የመሣሪያ ካሜራ እገዳ ይነሳ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"መተግበሪያ በስተቀኝ ላይ ሆኖ የተከፈለ ማያ ገፅን ይጠቀሙ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"መተግበሪያ በስተግራ ላይ ሆኖ የተከፈለ ማያ ገፅን ይጠቀሙ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ሙሉ ገፅ ዕይታን ይጠቀሙ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"የዴስክቶፕ ዕይታ ይጠቀሙ"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"የተከፈለ ማያ ገጽን ሲጠቀሙ በቀኝ ወይም ከታች ወዳለ መተግበሪያ ይቀይሩ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"የተከፈለ ማያ ገጽን ሲጠቀሙ በቀኝ ወይም ከላይ ወዳለ መተግበሪያ ይቀይሩ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"በተከፈለ ማያ ገጽ ወቅት፡- መተግበሪያን ከአንዱ ወደ ሌላው ተካ"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"አነስተኛ ቅድሚያ ያላቸው የማሳወቂያ አዶዎችን አሳይ"</string>
<string name="other" msgid="429768510980739978">"ሌላ"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"የርዕሱን መጠን ይቀያይሩ"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ሰቅ አስወግድ"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"በመጨረሻው ቦታ ላይ ሰቅ ያክሉ"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ሰቁን ውሰድ"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ወደ <xliff:g id="POSITION">%1$d</xliff:g> ቦታ አክል"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"አቀማመጡ ተቀባይነት የለውም።"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"የ<xliff:g id="POSITION">%1$d</xliff:g> አቀማመጥ"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ሰቅ ታክሏል"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ሰቅ ተወግዷል"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"የፈጣን ቅንብሮች አርታዒ።"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"የቅርብ ጊዜ መተግበሪያዎችን ለማየት የመዳሰሻ ሰሌዳው ላይ በሦስት ጣቶች ወደላይ ያንሸራትቱ እና ይያዙ"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"ሁሉንም መተግበሪያዎችዎን ለማየት በቁልፍ ሰሌዳዎ ላይ ያለውን የተግባር ቁልፍ ይጫኑ"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"ጽሁፍ ተቀይሯል"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ለመመልከት ይክፈቱ"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ኮድን ለመመልከት ይክፈቱ"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"የዓውድ ትምህርት"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ለመመለስ የመዳሰሻ ሰሌዳዎን ይጠቀሙ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ሦስት ጣቶችን በመጠቀም ወደ ግራ ወይም ወደ ቀኝ ያንሸራትቱ። ምልክቶችን የበለጠ ለማወቅ መታ ያድርጉ።"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ያልታወቀ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ሁሉም ሰቆች ዳግም ይጀምሩ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ሁሉም የፈጣን ቅንብሮች ሰቆች ወደ የመሣሪያው የመጀመሪያ ቅንብሮች ዳግም ይጀምራሉ"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>፣ <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 99c156b9aa2e..416eee837b22 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -245,20 +245,15 @@
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
<string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"رمز الجهاز الذي يتضمّن بلوتوث"</string>
<string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"انقر هنا لضبط إعدادات الجهاز."</string>
- <!-- no translation found for accessibility_bluetooth_device_settings_gear_with_name (114373701123165491) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_see_all (5260390270128256620) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (7988547106800504256) -->
- <skip />
+ <string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"‫<xliff:g id="DEVICE_NAME">%s</xliff:g>. ضبط تفاصيل الجهاز"</string>
+ <string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"عرض جميع الأجهزة"</string>
+ <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"إقران جهاز جديد"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"تم الاتصال بـ <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"سيتم توسيع المجموعة."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"إضافة الجهاز إلى المجموعة"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"إزالة الجهاز من المجموعة"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"سيتم فتح التطبيق."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"غير متصل."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"التجوال"</string>
@@ -336,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"الإدخال"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعات الأذن الطبية"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"جارٍ التفعيل…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"لا يمكن ضبط مستوى السطوع لأنّ التطبيق العلوي يتحكّم فيه"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"التدوير التلقائي"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"التدوير التلقائي للشاشة"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"الموقع الجغرافي"</string>
@@ -427,14 +421,19 @@
<string name="hearing_devices_preset_label" msgid="7878267405046232358">"الإعدادات المسبقة"</string>
<string name="hearing_devices_spinner_item_selected" msgid="3137083889662762383">"تمّ اختياره"</string>
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"الأصوات المحيطة"</string>
- <string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"الجهاز الأيسر"</string>
- <string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"الجهاز الأيمن"</string>
+ <string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"اليسرى"</string>
+ <string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"اليمنى"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"الأصوات المحيطة"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"الأصوات المحيطة بالجهة اليسرى"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"الأصوات المحيطة بالجهة اليمنى"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"توسيع لوحة التحكّم الموحّدة إلى عناصر تحكُّم منفصلة على اليسار واليمين"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"تصغير عناصر التحكّم في الصوت إلى لوحة تحكُّم موحّدة"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"كتم الأصوات المحيطة"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"إعادة الأصوات المحيطة"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"الأدوات"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"النسخ النصي التلقائي"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ملاحظات"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"هل تريد إزالة حظر ميكروفون الجهاز؟"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"هل تريد إزالة حظر كاميرا الجهاز؟"</string>
@@ -906,9 +905,8 @@
<string name="keyboard_shortcut_group_system_multitasking" msgid="6967816258924795558">"تعدُّد المهام"</string>
<string name="system_multitasking_rhs" msgid="8779289852395243004">"استخدام \"وضع تقسيم الشاشة\" مع تثبيت التطبيق على اليمين"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"استخدام \"وضع تقسيم الشاشة\" مع تثبيت التطبيق على اليسار"</string>
- <!-- no translation found for system_multitasking_full_screen (4221409316059910349) -->
- <skip />
- <!-- no translation found for system_multitasking_desktop_view (8829838918507805921) -->
+ <string name="system_multitasking_full_screen" msgid="4221409316059910349">"استخدام وضع ملء الشاشة"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
<skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"التبديل إلى التطبيق على اليسار أو الأسفل أثناء استخدام \"تقسيم الشاشة\""</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"التبديل إلى التطبيق على اليمين أو الأعلى أثناء استخدام \"تقسيم الشاشة\""</string>
@@ -1005,6 +1003,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"الإضافة إلى الموضع <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"الموضِع غير صالح."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"الموضع: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"تمت إضافة البطاقة."</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"تمت إزالة البطاقة."</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"برنامج تعديل الإعدادات السريعة."</string>
@@ -1511,8 +1511,7 @@
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"عرض التطبيقات المستخدَمة مؤخرًا"</string>
<string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"التبديل بين التطبيقات"</string>
<string name="touchpad_tutorial_done_button" msgid="176168488821755503">"تم"</string>
- <!-- no translation found for touchpad_tutorial_next_button (9169718126626806688) -->
- <skip />
+ <string name="touchpad_tutorial_next_button" msgid="9169718126626806688">"التالي"</string>
<string name="gesture_error_title" msgid="469064941635578511">"يُرجى إعادة المحاولة"</string>
<string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"رجوع"</string>
<string name="touchpad_back_gesture_guidance" msgid="5352221087725906542">"مرِّر سريعًا لليمين أو لليسار باستخدام 3 أصابع على لوحة اللمس"</string>
@@ -1574,4 +1573,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"غير معروفة"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"هل تريد إعادة ضبط كل المربّعات؟"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ستتم إعادة ضبط جميع مربّعات \"الإعدادات السريعة\" إلى الإعدادات الأصلية للجهاز"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"\"<xliff:g id="STREAM_NAME">%1$s</xliff:g>\"، <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 17d24da6d327..4340f2132e78 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>ৰ লগত সংযোগ কৰা হ’ল।"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>ত সংযোগ হ’ল।"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"গোট বিস্তাৰ কৰক।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"গোটত ডিভাইচ যোগ দিয়ক।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"গোটৰ পৰা ডিভাইচ আঁতৰাওক।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"এপ্লিকেশ্বনটো খোলক।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"সংযোগ হৈ থকা নাই।"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ৰ\'মিং"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"শ্ৰৱণ যন্ত্ৰ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"অন কৰি থকা হৈছে…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"উজ্জ্বলতা মিলাব নোৱাৰি কাৰণ সেয়া শীৰ্ষৰ এপটোৱে নিয়ন্ত্ৰণ কৰি আছে"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"স্বয়ং-ঘূৰ্ণন"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"অৱস্থান"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"আশ-পাশ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"বাওঁফাল"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"সোঁফাল"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"বাওঁ আৰু সোঁফালৰ পৃথক কৰা নিয়ন্ত্ৰণলৈ সংকোচন কৰক"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"একত্ৰিত নিয়ন্ত্ৰণলৈ সংকোচন কৰক"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"আশ-পাশৰ ধ্বনি মিউট কৰক"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"আশ-পাশৰ ধ্বনি আনমিউট কৰক"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"সঁজুলি"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"লাইভ কেপশ্বন"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"টোকা"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ডিভাইচৰ মাইক্ৰ\'ফ\'ন অৱৰোধৰ পৰা আঁতৰাবনে?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ডিভাইচৰ কেমেৰা অৱৰোধৰ পৰা আঁতৰাবনে?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"সোঁফালে থকা এপ্‌টোৰ সৈতে বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰক"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"বাওঁফালে থকা এপ্‌টোৰ সৈতে বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰক"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"পূৰ্ণ স্ক্ৰীন ব্যৱহাৰ কৰক"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ডেস্কটপ ভিউ ব্যৱহাৰ কৰক"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰাৰ সময়ত সোঁফালে অথবা তলত থকা এপলৈ সলনি কৰক"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰাৰ সময়ত বাওঁফালে অথবা ওপৰত থকা এপলৈ সলনি কৰক"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"বিভাজিত স্ক্ৰীনৰ ব্যৱহাৰ কৰাৰ সময়ত: কোনো এপ্ এখন স্ক্ৰীনৰ পৰা আনখনলৈ নিয়ক"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম গুৰুত্বপূৰ্ণ জাননীৰ আইকনসমূহ দেখুৱাওক"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"টাইলৰ আকাৰ ট’গল কৰক"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল আঁতৰাবলৈ"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"অন্তিম স্থানত টাইল যোগ দিয়ক"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল স্থানান্তৰ কৰক"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰ স্থানত যোগ দিয়ক"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"স্থান অমান্য।"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰ স্থান"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"টাইল যোগ দিয়া হৈছে"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"টাইল আঁতৰোৱা হৈছে"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ক্ষিপ্ৰ ছেটিঙৰ সম্পাদক।"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"শেহতীয়া এপ্‌সমূহ চাবলৈ টাচ্চপেডখনত তিনিটা আঙুলিৰে ওপৰলৈ ছোৱাইপ কৰি ধৰি ৰাখক"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"আপোনাৰ আটাইবোৰ এপ্‌ চাবলৈ আপোনাৰ কীব’ৰ্ডৰ কাৰ্য কীটোত টিপক"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"সম্পাদনা কৰা হৈছে"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"চাবলৈ আনলক কৰক"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ক’ড চাবলৈ আনলক কৰক"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"প্ৰাসংগিক শিক্ষা"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"উভতি যাবলৈ আপোনাৰ টাচ্চপেড ব্যৱহাৰ কৰক"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"তিনিটা আঙুলি ব্যৱহাৰ কৰি বাওঁফাললৈ বা সোঁফাললৈ ছোৱাইপ কৰক। অধিক নিৰ্দেশ শিকিবলৈ টিপক।"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"অজ্ঞাত"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"আটাইবোৰ টাইল ৰিছেট কৰিবনে?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"আটাইবোৰ ক্ষিপ্ৰ ছেটিঙৰ টাইল ডিভাইচৰ মূল ছেটিংছলৈ ৰিছেট হ’ব"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index a0eecd13b690..226c39dad194 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> cihazına qoşulub."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Qrupu genişləndirin."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Qrupa cihaz əlavə edin."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Cihazı qrupdan silin."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Tətbiqi açın."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Qoşulu deyil."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Rouminq"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eşitmə aparatları"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiv edilir..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Yuxarıdakı tətbiq tərəfindən idarə olunduğu üçün parlaqlığı tənzimləmək mümkün deyil"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avtodönüş"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranın avtomatik dönməsi"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Məkan"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ətraf mühit"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Sol"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Sağ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Sola və sağa ayrılmış idarəetmələr üçün genişləndirin"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Vahid nəzarət üçün yığcamlaşdırın"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ətraf mühiti səssiz edin"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ətraf mühiti səssiz rejimdən çıxarın"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Alətlər"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Canlı Altyazı"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Qeyd"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Cihaz mikrofonu blokdan çıxarılsın?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Cihaz kamerası blokdan çıxarılsın?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Tətbiq sağda olmaqla bölünmüş ekranı istifadə edin"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Tətbiq solda olmaqla bölünmüş ekranı istifadə edin"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Tam ekrandan istifadə edin"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Masaüstü görünüşdən istifadə edin"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Bölünmüş ekran istifadə edərkən sağda və ya aşağıda tətbiqə keçin"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bölünmüş ekran istifadə edərkən solda və ya yuxarıda tətbiqə keçin"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Bölünmüş ekran rejimində: tətbiqi birindən digərinə dəyişin"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Aşağı prioritet bildiriş işarələrini göstərin"</string>
<string name="other" msgid="429768510980739978">"Digər"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"mozaik ölçüsünü aktiv/deaktiv edin"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"lövhəni silin"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"son mövqeyə mozaik əlavə edin"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Lövhəni köçürün"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə əlavə edin"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Mövqe yanlışdır."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyi"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Mozaik əlavə edilib"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Mozaik silinib"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Sürətli ayarlar redaktoru."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Son tətbiqlərə baxmaq üçün taçpeddə üç barmağınızla yuxarı çəkib saxlayın"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Bütün tətbiqlərə baxmaq üçün klaviaturada fəaliyyət açarını basın"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Çıxarılıb"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Baxmaq üçün kiliddən çıxarın"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Koda baxmaq üçün kiliddən çıxarın"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstual təhsil"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Geri qayıtmaq üçün taçped istifadə edin"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Üç barmaqla sola və ya sağa çəkin. Daha çox jest öyrənmək üçün toxunun."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Naməlum"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Bütün mozaiklər sıfırlansın?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Bütün Sürətli Ayarlar mozaiki cihazın orijinal ayarlarına sıfırlanacaq"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 8921effd0cb6..0d7619d36e75 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezani ste sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani smo sa uređajem <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Proširite grupu."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodajte uređaj u grupu."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Uklonite uređaj iz grupe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otvorite aplikaciju."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nije povezano."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključuje se..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ne možete da prilagodite osvetljenost jer je kontroliše aplikacija u prvom planu"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatska rotacija"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okruženje"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Levo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Proširi na kontrole razdvojene na levu i desnu stranu"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Skupi u jedinstvenu kontrolu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Isključi zvuk okruženja"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Uključi zvuk okruženja"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Alatke"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Titl uživo"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Beleška"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite da odblokirate mikrofon uređaja?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Želite da odblokirate kameru uređaja?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Koristi podeljeni ekran sa aplikacijom s desne strane"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Koristi podeljeni ekran sa aplikacijom s leve strane"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Koristi prikaz preko celog ekrana"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Koristi prikaz za računare"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Pređi u aplikaciju zdesna ili ispod dok je podeljen ekran"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pređite u aplikaciju sleva ili iznad dok koristite podeljeni ekran"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"U režimu podeljenog ekrana: zamena jedne aplikacije drugom"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajte na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicija je nevažeća."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozicija"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pločica je dodata"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pločica je uklonjena"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivač za Brza podešavanja."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite da resetujete sve pločice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve pločice Brzih podešavanja će se resetovati na prvobitna podešavanja uređaja"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3568586a75b6..809b60b712a1 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Падлучаны да <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Ёсць падключэнне да <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Разгарнуць групу."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Дадаць прыладу ў групу."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Выдаліць прыладу з групы."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Адкрыць праграму."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Няма падключэння."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роўмінг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Увод"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слыхавыя апараты"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Уключэнне…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не ўдаецца адрэгуляваць яркасць, бо яна кантралюецца верхняй праграмай"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аўтапаварот"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аўтаматычны паварот экрана"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Месцазнаходжанне"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Навакольныя гукі"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Левы бок"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Правы бок"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Зрабіць левую і правую панэлі кіравання"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Зрабіць адну панэль кіравання"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Выключыць навакольныя гукі"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Уключыць навакольныя гукі"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Інструменты"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Аўтаматычныя субцітры"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Нататка"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Разблакіраваць мікрафон прылады?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Разблакіраваць камеру прылады?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Падзяліць экран і памясціць праграму справа"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Падзяліць экран і памясціць праграму злева"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Выкарыстоўваць поўнаэкранны рэжым"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Выкарыстоўваць версію для камп’ютараў"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Пераключыцца на праграму справа або ўнізе на падзеленым экране"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Пераключыцца на праграму злева або ўверсе на падзеленым экране"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"У рэжыме падзеленага экрана замяніць адну праграму на іншую"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Паказваць значкі апавяшчэнняў з нізкім прыярытэтам"</string>
<string name="other" msgid="429768510980739978">"Іншае"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"змяніць памер пліткі"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"выдаліць плітку"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"дадаць плітку ў апошнюю пазіцыю"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перамясціць плітку"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Дадаць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Няправільнае месца."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Пазіцыя <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плітка дададзена"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плітка выдалена"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Рэдактар хуткіх налад."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Для прагляду нядаўніх праграм правядзіце па сэнсарнай панэлі трыма пальцамі ўверх і затрымайцеся"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Каб праглядзець усе праграмы, націсніце на клавішу дзеяння на клавіятуры"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Схавана"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Разблакіруйце экран, каб праглядзець"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Разблакіруйце экран, каб праглядзець код"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Кантэкстнае навучанне"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Выкарыстайце сэнсарную панэль для вяртання"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Правядзіце ўлева ці ўправа трыма пальцамі. Націсніце, каб азнаёміцца з іншымі жэстамі."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Невядома"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Скінуць усе пліткі?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Усе пліткі хуткіх налад будуць скінуты да першапачатковых налад прылады"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 9d6be139efd2..9f7a9cdda45b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Има връзка с <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Установена е връзка с/ъс <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Разгъване на групата."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Добавяне на устройството към групата."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Премахване на устройството от групата."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Отваряне на приложението."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Няма връзка."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Вход"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухови апарати"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включва се..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Яркостта не може да се коригира, защото се контролира от приложението на екрана"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авт. ориентация"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично завъртане на екрана"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Местоположение"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Околни звуци"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ляво"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Дясно"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Околни звуци"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Околни звуци отляво"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Околни звуци отдясно"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Разгъване до отделни контроли за ляво и дясно"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Свиване до обединена контрола"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Спиране на околните звуци"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Включване на околните звуци"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Инструменти"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Надписи на живо"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Бележка"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Да се отблокира ли микрофонът на устройството?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Да се отблокира ли камерата на устройството?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Използване на разделен екран с приложението вдясно"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Използване на разделен екран с приложението вляво"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Използване режима на цял екран"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Използване на изгледа за настолни компютри"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Превключване към приложението вдясно/отдолу в режима на разделен екран"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Превключване към приложението вляво/отгоре в режима на разделен екран"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"При разделен екран: замяна на дадено приложение с друго"</string>
@@ -1000,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавяне към позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Невалидна позиция."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Панелът вече е добавен"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Панелът е добавен"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Панелът е премахнат"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор за бързи настройки."</string>
@@ -1568,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Неизвестно"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Да се нулират ли всички панели?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Всички панели с бързи настройки ще бъдат нулирани до първоначалните настройки на устройството"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g> – <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 636d4f28c18a..f1d97700c6f3 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>এ সংযুক্ত হয়ে আছে।"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> এর সাথে সংযুক্ত৷"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"গ্রুপ বড় করুন।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"গ্রুপে ডিভাইস যোগ করুন।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"গ্রুপ থেকে ডিভাইস সরিয়ে দিন।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"অ্যাপ্লিকেশন খুলুন।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"সংযুক্ত নয়৷"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"রোমিং"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"হিয়ারিং এড"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"চালু করা হচ্ছে…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"উজ্জ্বলতা অ্যাডজাস্ট করা যাচ্ছে না কারণ এটি সেরা অ্যাপের মাধ্যমে কন্ট্রোল করা হচ্ছে"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"নিজে থেকে ঘুরবে"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"অটো-রোটেট স্ক্রিন"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"লোকেশন"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"সারাউন্ডিং"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"বাঁদিক"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ডানদিক"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"বাঁদিক ও ডানদিকের আলাদা করা কন্ট্রোল বড় করুন"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ইউনিফায়েড কন্ট্রোল আড়াল করুন"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"সারাউন্ডিং মিউট করুন"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"সারাউন্ডিং আনমিউট করুন"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"টুল"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"লাইভ ক্যাপশন"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"মনে রাখবেন"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ডিভাইসের মাইক্রোফোন আনব্লক করতে চান?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ডিভাইসের ক্যামেরা আনব্লক করতে চান?"</string>
@@ -582,7 +587,7 @@
<string name="media_projection_entry_app_permission_dialog_continue_entire_screen" msgid="1850848182344377579">"স্ক্রিন শেয়ার করুন"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"<xliff:g id="APP_NAME">%1$s</xliff:g> এই বিকল্পটি বন্ধ করে দিয়েছে"</string>
<string name="media_projection_entry_share_app_selector_title" msgid="1419515119767501822">"শেয়ার করার জন্য অ্যাপ বেছে নিন"</string>
- <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"আপনার স্ক্রিন কাস্ট করুন?"</string>
+ <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"আপনার স্ক্রিন কাস্ট করতে চান?"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"একটি অ্যাপ কাস্ট করুন"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"সম্পূর্ণ স্ক্রিন কাস্ট করুন"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"আপনি সম্পূর্ণ স্ক্রিন কাস্ট করলে, আপনার স্ক্রিনে থাকা সব কিছুই দেখা যাবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ডানদিকে বর্তমান অ্যাপে স্প্লিট স্ক্রিন ব্যবহার করুন"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"বাঁদিকে বর্তমান অ্যাপে স্প্লিট স্ক্রিন ব্যবহার করুন"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ফুল-স্ক্রিন মোড ব্যবহার করুন"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ডেস্কটপ ভিউ ব্যবহার করুন"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"স্প্লিট স্ক্রিন ব্যবহার করার সময় ডানদিকের বা নিচের অ্যাপে পাল্টে নিন"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"স্প্লিট স্ক্রিন ব্যবহার করার সময় বাঁদিকের বা উপরের অ্যাপে পাল্টে নিন"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"\'স্প্লিট স্ক্রিন\' থাকাকালীন: একটি অ্যাপ থেকে অন্যটিতে পাল্টান"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম-গুরুত্বপূর্ণ বিজ্ঞপ্তির আইকন দেখুন"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"টাইলের সাইজ টগল করুন"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল সরান"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"শেষ জায়গাতে টাইল যোগ করুন"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল সরান"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>-এ যোগ করুন"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"পজিশন সঠিক নয়।"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"টাইল যোগ করা হয়েছে"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"টাইল সরানো হয়েছে"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"দ্রুত সেটিংস সম্পাদক৷"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"সম্প্রতি ব্যবহার করা অ্যাপ দেখতে, টাচপ্যাডে তিনটি আঙুল ব্যবহার করে উপরের দিকে সোয়াইপ করে ধরে রাখুন"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"আপনার সব অ্যাপ দেখতে, কীবোর্ডে অ্যাকশন কী প্রেস করুন"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"রিড্যাক্ট করা হয়েছে"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"দেখার জন্য আনলক করুন"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"কোড দেখার জন্য আনলক করুন"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"প্রাসঙ্গিক শিক্ষা"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ফিরে যেতে টাচপ্যাড ব্যবহার করুন"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"তিনটি আঙুলের ব্যবহার করে ডান বা বাঁদিকে সোয়াইপ করুন। আরও জেসচার সম্পর্কে জানতে ট্যাপ করুন।"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"অজানা"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"সব টাইল রিসেট করবেন?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"সব কুইক সেটিংস টাইল, ডিভাইসের আসল সেটিংসে রিসেট হয়ে যাবে"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 331bac2fb24e..57b83d933f53 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezan na <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Povezan na <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Proširivanje grupe."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodavanje uređaja u grupu."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Uklanjanje uređaja iz grupe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otvaranje aplikacije."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nije povezano."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ulaz"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nije moguće podesiti osvijetljenost jer njome upravlja gornja aplikacija"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko rotiranje"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okruženje"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lijevo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Proširivanje u zasebne kontrole ulijevo i udesno"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sužavanje u objedinjenu kontrolu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Isključivanje zvuka okruženja"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Uključivanje zvuka okruženja"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Alati"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Automatski titlovi"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Bilješka"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblokirati mikrofon uređaja?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblokirati kameru uređaja?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Korištenje podijeljenog ekrana s aplikacijom na desnoj strani"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Korištenje podijeljenog ekrana s aplikacijom na lijevoj strani"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Korištenje prikaza preko cijelog ekrana"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Korištenje prikaza na računaru"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Prelazak u aplikaciju desno ili ispod uz podijeljeni ekran"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pređite u aplikaciju lijevo ili iznad dok koristite podijeljeni ekran"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Za vrijeme podijeljenog ekrana: zamjena jedne aplikacije drugom"</string>
@@ -991,7 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obavještenja niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Ostalo"</string>
- <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"promjenu veličine pločice"</string>
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"uključivanje/isključivanje veličine kartice"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"uklanjanje kartice"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"dodavanje kartice na posljednji položaj"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pomjeranje kartice"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nevažeći položaj."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivanje brzih postavki"</string>
@@ -1544,8 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Da pregledate nedavne aplikacije, prevucite nagore i zadržite s tri prsta na dodirnoj podlozi"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Da pregledate sve aplikacije, pritisnite tipku radnji na tastaturi"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redigovano"</string>
- <string name="public_notification_single_line_text" msgid="3576190291791654933">"Otključajte za prikaz"</string>
- <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Otključajte za prikaz koda"</string>
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Otključajte da pogledate"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Otključajte da pogledate kôd"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstualno obrazovanje"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Koristite dodirnu podlogu da se vratite"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Prevucite ulijevo ili udesno s tri prsta. Dodirnite da naučite više pokreta."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vratiti sve kartice na zadano?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve kartice Brze postavke će se vratiti na originalne postavke uređaja"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 01d769249d9f..d2f942eeb628 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"S\'ha connectat a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Està connectat amb <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Desplega el grup."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Afegeix el dispositiu al grup."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Suprimeix el dispositiu del grup."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Obre l\'aplicació."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Sense connexió."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Itinerància"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiòfons"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"S\'està activant…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"No es pot ajustar la brillantor perquè està controlada per l\'aplicació superior"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Gira automàticament"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Gira la pantalla automàticament"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicació"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Entorn"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Esquerra"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dreta"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Entorn"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Entorn esquerre"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Entorn dret"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Desplega els controls separats d\'esquerra i dreta"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Replega per unificar el control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silencia l\'entorn"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Deixa de silenciar l\'entorn"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Eines"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtítols instantanis"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vols desbloquejar el micròfon del dispositiu?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vols desbloquejar la càmera del dispositiu?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Utilitzar la pantalla dividida amb l\'aplicació a la dreta"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Utilitzar la pantalla dividida amb l\'aplicació a l\'esquerra"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Utilitza la pantalla completa"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Utilitza la visualització per a ordinadors"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Canvia a l\'aplicació de la dreta o de sota amb la pantalla dividida"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Canvia a l\'aplicació de l\'esquerra o de dalt amb la pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Durant el mode de pantalla dividida: substitueix una app per una altra"</string>
@@ -1000,6 +1003,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Afegeix a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"La posició no és vàlida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"El mosaic s\'ha afegit"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"El mosaic s\'ha suprimit"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configuració ràpida."</string>
@@ -1568,4 +1573,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconegut"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vols restablir totes les icones?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Totes les icones de configuració ràpida es restabliran a les opcions originals del dispositiu"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 15e4703cb702..5149f9d8c1af 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Připojeno k zařízení <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Jste připojeni k zařízení <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Rozbalit skupinu."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Přidat zařízení do skupiny."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Odstranit zařízení ze skupiny."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otevřít aplikaci."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nepřipojeno."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Naslouchátka"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapínání…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Jas nelze upravit, protože ho řídí hlavní aplikace"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. otáčení"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčení obrazovky"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Poloha"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okolí"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vlevo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Vpravo"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Rozbalit na samostatné ovládání levé a pravé strany"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sbalit na sjednocené ovládání"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ztlumit okolí"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Zapnout zvuk okolí"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Nástroje"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Okamžité titulky"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Poznámka"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Odblokovat mikrofon zařízení?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Odblokovat fotoaparát zařízení?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Použít rozdělenou obrazovku s aplikací vpravo"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Použít rozdělenou obrazovku s aplikací vlevo"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Použít celou obrazovku"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Použít zobrazení na počítači"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Přepnout na aplikaci vpravo nebo dole v režimu rozdělené obrazovky"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Přepnout na aplikaci vlevo nebo nahoře v režimu rozdělené obrazovky"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"V režimu rozdělené obrazovky: nahradit jednu aplikaci druhou"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Zobrazit ikony oznámení s nízkou prioritou"</string>
<string name="other" msgid="429768510980739978">"Jiné"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"přepnout velikost dlaždice"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranit dlaždici"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"přidat dlaždici na poslední pozici"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Přesunout dlaždici"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Přidat dlaždici na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozice není platná."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Dlaždice byla přidána"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Dlaždice byla odstraněna"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rychlého nastavení"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Pokud chcete zobrazit poslední aplikace, přejeďte na touchpadu třemi prsty nahoru a podržte je"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Pokud chcete zobrazit všechny aplikace, stiskněte na klávesnici akční klávesu"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Odstraněno"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"K zobrazení je potřeba zařízení odemknout"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Pokud chcete zobrazit kód, odemkněte zařízení"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextová výuka"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Návrat zpět pomocí touchpadu"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Přejeďte třemi prsty doleva nebo doprava. Další gesta zjistíte klepnutím."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznámé"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Resetovat všechny dlaždice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Všechny dlaždice Rychlého nastavení se resetují do původní konfigurace zařízení"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index ab87c4672797..baf010ddae5d 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Forbundet med <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Forbundet til <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Udvid gruppe."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Føj enhed til gruppe."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Fjern enhed fra gruppe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Åbn app."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Ikke tilsluttet."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverer…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Lysstyrken kan ikke justeres, fordi den styres af den øverste app"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Roter automatisk"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Roter skærmen automatisk"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokation"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgivelser"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Venstre"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Højre"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Udvid til adskilte styringselementer til venstre og højre"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Minimer til samlet styringselement"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ignorer omgivelser"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ignorer ikke omgivelser"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Værktøjer"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Livetekstning"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vil du fjerne blokeringen af enhedens mikrofon?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vil du fjerne blokeringen af enhedens kamera?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Brug opdelt skærm med appen til højre"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Brug opdelt skærm med appen til venstre"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Brug fuld skærm"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Brug computervenlig visning"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Skift til en app til højre eller nedenfor, når du bruger opdelt skærm"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skift til en app til venstre eller ovenfor, når du bruger opdelt skærm"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ved opdelt skærm: Udskift én app med en anden"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for notifikationer med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Andet"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ændre feltets størrelse"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjern felt"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"føj handlingsfeltet til den sidste position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flyt felt"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Føj til lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen er ugyldig."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Feltet blev tilføjet"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Feltet blev fjernet"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigeringsværktøj til Kvikmenu."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Du kan se nyligt brugte apps ved at stryge opad og holde tre fingre nede på touchpladen"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Du kan se alle dine apps ved at trykke på handlingstasten på dit tastatur"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Skjult"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Lås op for at se"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Lås op for at se koden"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstbaseret uddannelse"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Brug din touchplade til at gå tilbage"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Stryg til venstre eller højre med tre fingre. Tryk for at lære flere bevægelser."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ukendt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vil du nulstille alle handlingsfelter?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle handlingsfelter i kvikmenuen nulstilles til enhedens oprindelige indstillinger"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 99e32ecd3d52..8c8e994dd9cc 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Mit <xliff:g id="BLUETOOTH">%s</xliff:g> verbunden"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Verbunden mit <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Gruppe erweitern."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Gerät zu Gruppe hinzufügen."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Gerät aus Gruppe entfernen."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Anwendung öffnen."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nicht verbunden"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Eingabe"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörgerät"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Wird aktiviert…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Die Helligkeit kann nicht angepasst werden, weil sie von der obersten App gesteuert wird"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. drehen"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Bildschirm automatisch drehen"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Standort"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Umgebungsgeräusche"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Links"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Rechts"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"In ein linkes und ein rechtes Steuerfeld maximieren"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Zu einem einzigen Steuerfeld minimieren"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Umgebungsgeräusche stummschalten"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Stummschaltung der Umgebungsgeräusche aufheben"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tools"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Automatische Untertitel"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Notiz"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Blockierung des Gerätemikrofons aufheben?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Blockierung der Gerätekamera aufheben?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Splitscreen mit der App auf der rechten Seite nutzen"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Splitscreen mit der App auf der linken Seite nutzen"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Vollbildmodus verwenden"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Desktop-Ansicht verwenden"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Im Splitscreen-Modus zu einer App rechts oder unten wechseln"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Im Splitscreen-Modus zu einer App links oder oben wechseln"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Im Splitscreen: eine App durch eine andere ersetzen"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Symbole für Benachrichtigungen mit einer niedrigen Priorität anzeigen"</string>
<string name="other" msgid="429768510980739978">"Sonstiges"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"Größe der Kachel umschalten"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"Entfernen der Kachel"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"Kachel an letzter Position hinzufügen"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Kachel verschieben"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Zur Position <xliff:g id="POSITION">%1$d</xliff:g> hinzufügen"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position ist ungültig."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ansicht hinzugefügt"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ansicht entfernt"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor für Schnelleinstellungen."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Um zuletzt verwendete Apps aufzurufen, wische mit 3 Fingern nach oben und halte das Touchpad gedrückt"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Wenn du alle deine Apps aufrufen möchtest, drücke auf der Tastatur die Aktionstaste"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Entfernt"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Zum Ansehen entsperren"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Zum Ansehen des Codes entsperren"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextbezogene Informationen"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Zum Zurückgehen Touchpad verwenden"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Wische mit drei Fingern nach links oder rechts. Tippe für mehr Infos zu Touch-Gesten."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unbekannt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Alle Kacheln zurücksetzen?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle Schnelleinstellungen-Kacheln werden auf die Standardeinstellungen des Geräts zurückgesetzt"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index d3bfc99bea7a..daebf7006498 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Συνδέθηκε στο <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Συνδέθηκε σε <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Αναπτύξτε την ομάδα."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Προσθήκη συσκευής στην ομάδα."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Κατάργηση συσκευής από την ομάδα."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Ανοίξτε την εφαρμογή."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Μη συνδεδεμένο"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Περιαγωγή"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Είσοδος"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Βοηθήματα ακοής"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ενεργοποίηση…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Δεν είναι δυνατή η προσαρμογή της φωτεινότητας, επειδή ελέγχεται από την εφαρμογή στην κορυφή"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Αυτόματη περιστροφή"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Αυτόματη περιστροφή οθόνης"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Τοποθεσία"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ήχοι περιβάλλοντος"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Αριστερά"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Δεξιά"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Ανάπτυξη σε ξεχωριστά στοιχεία ελέγχου αριστερά και δεξιά"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Σύμπτυξη σε ενοποιημένο στοιχείο ελέγχου"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Σίγαση ήχων περιβάλλοντος"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Κατάργηση σίγασης ήχων περιβάλλοντος"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Εργαλεία"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Ζωντανοί υπότιτλοι"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Σημείωση"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Κατάργηση αποκλεισμού μικροφώνου συσκευής;"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Κατάργηση αποκλεισμού κάμερας συσκευής;"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Χρήση διαχωρισμού οθόνης με την εφαρμογή στα δεξιά"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Χρήση διαχωρισμού οθόνης με την εφαρμογή στα αριστερά"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Χρήση πλήρους οθόνης"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Χρήση προβολής για υπολογιστές"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Εναλλαγή στην εφαρμογή δεξιά ή κάτω κατά τη χρήση διαχωρισμού οθόνης"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Εναλλαγή σε εφαρμογή αριστερά ή επάνω κατά τη χρήση διαχωρισμού οθόνης"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Κατά τον διαχωρισμό οθόνης: αντικατάσταση μιας εφαρμογής με άλλη"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Προσθήκη στη θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Μη έγκυρη θέση."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Το πλακίδιο προστέθηκε"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Το πλακίδιο καταργήθηκε"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Επεξεργασία γρήγορων ρυθμίσεων."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Άγνωστο"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Επαναφορά σε όλα τα πλακάκια;"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Σε όλα τα πλακάκια Γρήγορων ρυθμίσεων θα γίνει επαναφορά στις αρχικές ρυθμίσεις της συσκευής"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index c2537110ba58..875b21a72166 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expand group."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Add device to group."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remove device from group."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Open application."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Not connected."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Surroundings"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Left surroundings"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Right surroundings"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Unmute surroundings"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tools"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live Caption"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Use split screen with app on the right"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Use split screen with app on the left"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Use fullscreen"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Use desktop view"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Switch to the app on the right or below while using split screen"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"toggle the tile\'s size"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"add tile to the last position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
@@ -1001,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tile already added"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string>
@@ -1545,10 +1548,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"To view recent apps, swipe up and hold with three fingers on the touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"To view all your apps, press the action key on your keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redacted"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Unlock to view"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Unlock to view code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextual education"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use your touchpad to go back"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe left or right using three fingers. Tap to learn more gestures."</string>
@@ -1571,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 92e9d8d00b9d..4e4295ab56a2 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -423,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Surroundings"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Left surroundings"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Right surroundings"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Unmute surroundings"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tools"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live Caption"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"Settings"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string>
@@ -901,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Use split screen with app on the right"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Use split screen with app on the left"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Use full screen"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Use desktop view"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Switch to app on right or below while using split screen"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to app on left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: replace an app from one to another"</string>
@@ -997,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tile already added"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string>
@@ -1565,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device’s original settings"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index c2537110ba58..875b21a72166 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expand group."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Add device to group."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remove device from group."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Open application."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Not connected."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Surroundings"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Left surroundings"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Right surroundings"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Unmute surroundings"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tools"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live Caption"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Use split screen with app on the right"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Use split screen with app on the left"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Use fullscreen"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Use desktop view"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Switch to the app on the right or below while using split screen"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"toggle the tile\'s size"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"add tile to the last position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
@@ -1001,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tile already added"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string>
@@ -1545,10 +1548,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"To view recent apps, swipe up and hold with three fingers on the touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"To view all your apps, press the action key on your keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redacted"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Unlock to view"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Unlock to view code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextual education"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use your touchpad to go back"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe left or right using three fingers. Tap to learn more gestures."</string>
@@ -1571,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index c2537110ba58..875b21a72166 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expand group."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Add device to group."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remove device from group."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Open application."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Not connected."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Surroundings"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Left surroundings"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Right surroundings"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Unmute surroundings"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tools"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live Caption"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Use split screen with app on the right"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Use split screen with app on the left"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Use fullscreen"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Use desktop view"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Switch to the app on the right or below while using split screen"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"toggle the tile\'s size"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"add tile to the last position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
@@ -1001,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tile already added"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string>
@@ -1545,10 +1548,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"To view recent apps, swipe up and hold with three fingers on the touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"To view all your apps, press the action key on your keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redacted"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Unlock to view"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Unlock to view code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextual education"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use your touchpad to go back"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe left or right using three fingers. Tap to learn more gestures."</string>
@@ -1571,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 7f096712c6b8..c2dd040426a8 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expandir grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Agregar el dispositivo al grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Quitar el dispositivo del grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicación."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"No conectado"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"La app superior controla el brillo, por lo que no se puede ajustar"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar la pantalla automáticamente"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Sonido envolvente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Izquierda"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Derecha"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Sonido envolvente"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Entorno de la izquierda"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Entorno de la derecha"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expandir los controles separados a la izquierda y a la derecha"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Contraer al control unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar el sonido envolvente"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Activar el sonido envolvente"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Herramientas"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtitulado instantáneo"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"Configuración"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"¿Quieres desbloquear el micrófono del dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"¿Quieres desbloquear la cámara del dispositivo?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Usar la pantalla dividida con la app a la derecha"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Usar la pantalla dividida con la app a la izquierda"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Usar la pantalla completa"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Usar la vista para computadoras de escritorio"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Ubicar la app a la derecha o abajo cuando usas la pantalla dividida"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Ubicar la app a la izquierda o arriba cuando usas la pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Durante pantalla dividida: Reemplaza una app con otra"</string>
@@ -1000,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Agregar a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tarjeta ya agregada"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Se agregó la tarjeta"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Se quitó la tarjeta"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de Configuración rápida"</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconocido"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"¿Quieres restablecer todas las tarjetas?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Se restablecerán todas las tarjeta de Configuración rápida a la configuración original del dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 531d1df02532..7902db813708 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Mostrar grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Añade un dispositivo al grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Quita un dispositivo del grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicación."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"No conectado."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"No se puede ajustar el brillo porque la aplicación superior lo está controlando"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar pantalla automáticamente"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Alrededores"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Izquierda"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Derecha"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expandir a los controles separados de izquierda y derecha"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Contraer al control unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar alrededores"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Dejar de silenciar alrededores"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Herramientas"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtítulos automáticos"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"¿Desbloquear el micrófono del dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"¿Desbloquear la cámara del dispositivo?"</string>
@@ -588,7 +593,7 @@
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Cuando envías toda tu pantalla, se ve todo lo que hay en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Cuando envías una aplicación, se ve todo lo que se muestre o reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Enviar pantalla"</string>
- <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Elegir una aplicación para enviar"</string>
+ <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Elige una aplicación para enviar"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"¿Empezar a compartir?"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Usar la pantalla dividida con la aplicación a la derecha"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Usar la pantalla dividida con la aplicación a la izquierda"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Usar pantalla completa"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Usar vista para ordenador"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Cambiar a la aplicación de la derecha o de abajo en pantalla dividida"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Cambiar a la app de la izquierda o de arriba en pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Con pantalla dividida: reemplazar una aplicación por otra"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar iconos de notificaciones con prioridad baja"</string>
<string name="other" msgid="429768510980739978">"Otros"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"mostrar el tamaño del recuadro"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"quitar recuadro"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"añadir el recuadro a la última posición"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover recuadro"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Añadir a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Recuadro añadido"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Recuadro quitado"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de ajustes rápidos."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Para ver las aplicaciones recientes, desliza hacia arriba y mantén pulsado el panel táctil con tres dedos"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Para ver todas tus aplicaciones, pulsa la tecla de acción de tu teclado"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Oculta"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desbloquea para ver"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desbloquea para ver el código"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educación contextual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Usa el panel táctil para volver atrás"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Desliza hacia la izquierda o hacia la derecha con tres dedos. Toca para aprender a usar más gestos."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconocido"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"¿Borrar todos los recuadros?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos los recuadros de ajustes rápidos se restablecerán a los ajustes originales del dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 30b93b0dc578..723a3eb1f401 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ühendatud: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Ühendatud ülekandega <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Grupi laiendamine."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Seadme lisamine gruppi."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Seadme eemaldamine grupist."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Rakenduse avamine."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Ühendus puudub."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Rändlus"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sisend"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuuldeaparaadid"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Sisselülitamine …"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Heledust ei saa reguleerida, kuna seda juhib ülemine rakendus"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. pööramine"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Kuva automaatne pööramine"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Asukoht"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ümbritsevad helid"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vasakule"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Paremale"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Vasaku ja parema poole eraldi juhtimine"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Mõlema poole ühtne juhtimine"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ümbritsevate helide vaigistamine"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ümbritsevate helide vaigistuse tühistamine"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tööriistad"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Reaalajas subtiitrid"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Märkus"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Kas tühistada seadme mikrofoni blokeerimine?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Kas tühistada seadme kaamera blokeerimine?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Jagatud ekraanikuva kasutamine, rakendus kuvatakse paremal"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Jagatud ekraanikuva kasutamine, rakendus kuvatakse vasakul"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Lülita täisekraanile"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Kasuta arvutivaadet"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Paremale või alumisele rakendusele lülitamine jagatud ekraani ajal"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Vasakule või ülemisele rakendusele lülitamine jagatud ekraani ajal"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ekraanikuva jagamise ajal: ühe rakenduse asendamine teisega"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Kuva madala prioriteediga märguande ikoonid"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"muutke paani suurust"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"paani eemaldamiseks"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"lisage paan viimasesse asukohta"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Teisalda paan"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Sobimatu asukoht."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Asend <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Paan on lisatud"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Paan on eemaldatud"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kiirseadete redigeerija."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Hiljutiste rakenduste kuvamiseks pühkige puuteplaadil kolme sõrmega üles ja hoidke sõrmi puuteplaadil."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Kõigi oma rakenduste kuvamiseks vajutage klaviatuuril toiminguklahvi"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Peidetud"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Vaatamiseks avage"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Koodi vaatamiseks avage"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstipõhised õpetused"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Puuteplaadi kasutamine tagasiliikumiseks"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Pühkige kolme sõrmega vasakule või paremale. Puudutage liigutuste kohta lisateabe saamiseks."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Teadmata"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Kas lähtestada kõik paanid?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Kõik kiirseadete paanid lähtestatakse seadme algseadetele"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 50f875013255..55b223ae5ac1 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Hona konektatuta: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Zabaldu taldea."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Gehitu gailua taldera"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Kendu gailua taldetik."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Ireki aplikazioa."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Konektatu gabe."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Ibiltaritza"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sarrera"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audifonoak"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktibatzen…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ezin da argitasuna doitu goiko aplikazioak kontrolatzen duelako"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Biratze automatikoa"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Biratu pantaila automatikoki"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Kokapena"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ingurunea"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ezkerrekoa"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Eskuinekoa"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Zabaldu ezkerreko eta eskuineko kontrolatzeko aukerak bereiz erabiltzeko"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Tolestu kontrolatzeko aukerak bateratuta erabiltzeko"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Desaktibatu ingurunearen audioa"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Aktibatu ingurunearen audioa"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tresnak"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Istanteko azpitituluak"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Oharra"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Gailuaren mikrofonoa desblokeatu nahi duzu?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Gailuaren kamera desblokeatu nahi duzu?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Erabili pantaila zatitua eta ezarri aplikazio hau eskuinean"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Erabili pantaila zatitua eta ezarri aplikazio hau ezkerrean"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Erabili pantaila osoa"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Erabili ordenagailuetarako ikuspegia"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Aldatu eskuineko edo beheko aplikaziora pantaila zatitua erabiltzean"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Aldatu ezkerreko edo goiko aplikaziora pantaila zatitua erabiltzean"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Pantaila zatituan zaudela, ordeztu aplikazio bat beste batekin"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Erakutsi lehentasun txikiko jakinarazpenen ikonoak"</string>
<string name="other" msgid="429768510980739978">"Beste bat"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"aldatu lauzaren tamaina"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"kendu lauza"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"gehitu lauza azken posizioan"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mugitu lauza"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren lekuan"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kokapenak ez du balio."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>garren lekua"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Gehitu da lauza"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kendu da lauza"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ezarpen bizkorren editorea."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Azkenaldiko aplikazioak ikusteko, pasatu 3 hatz ukipen-panelean gora eta eduki sakatuta"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Aplikazio guztiak ikusteko, sakatu teklatuko ekintza-tekla"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Desitxuratuta"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desblokeatu ikusteko"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desblokeatu kodea ikusteko"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Testuinguruaren araberako hezkuntza"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Erabili ukipen-panela atzera egiteko"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Pasatu 3 hatz ezkerrera edo eskuinera. Sakatu keinu gehiago ikasteko."</string>
@@ -1571,4 +1576,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ezezagunak"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Lauza guztiak berrezarri nahi dituzu?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Gailuaren jatorrizko ezarpenak berrezarriko dira ezarpen bizkorren lauza guztietan"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index d53805d5b376..c28ec975e931 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"به <xliff:g id="BLUETOOTH">%s</xliff:g> متصل شد."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"به <xliff:g id="CAST">%s</xliff:g> متصل شد."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"گروه را از هم باز می‌کند."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"افزودن دستگاه به گروه."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"حذف دستگاه از گروه."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"برنامه را باز می‌کند."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"متصل نیست."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"فراگردی"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ورودی"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سمعک"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"روشن کردن…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"نمی‌توان روشنایی را تنظیم کرد زیرا برنامه بالایی آن را کنترل می‌کند"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"چرخش خودکار"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"چرخش خودکار صفحه‌نمایش"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"مکان"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"پیرامون"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"چپ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"راست"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ازهم بازکردن برای کنترل‌های جداگانه چپ و راست"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"جمع کردن برای کنترل یکپارچه"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"بی‌صدا کردن پیرامون"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"صدادار کردن پیرامون"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ابزارها"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"زیرنویس زنده ناشنوایان"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"یادداشت"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"میکروفون دستگاه لغو انسداد شود؟"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"دوربین دستگاه لغو انسداد شود؟"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"استفاده از صفحهٔ دونیمه با قرار گرفتن برنامه در سمت راست"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"استفاده از صفحهٔ دونیمه با قرار گرفتن برنامه در سمت چپ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"استفاده از حالت تمام‌صفحه"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"استفاده از نمای ویژه رایانه رومیزی"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"رفتن به برنامه سمت راست یا پایین درحین استفاده از صفحهٔ دونیمه"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"رفتن به برنامه سمت چپ یا بالا درحین استفاده از صفحهٔ دونیمه"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"درحین صفحهٔ دونیمه: برنامه‌ای را با دیگری جابه‌جا می‌کند"</string>
@@ -1000,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"افزودن به موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"موقعیت نامعتبر است."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"کاشی قبلاً اضافه شده است"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"کاشی اضافه شد"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"کاشی حذف شد"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ویرایشگر تنظیمات سریع."</string>
@@ -1568,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"نامشخص"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"همه کاشی‌ها بازنشانی شود؟"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"همه کاشی‌های «تنظیمات فوری» به تنظیمات اصلی دستگاه بازنشانی خواهد شد"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>، <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index fa9468df5fa0..98f4883fe880 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -254,10 +254,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Yhteys: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Yhdistetty kohteeseen <xliff:g id="CAST">%s</xliff:g>"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Laajenna ryhmä."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Lisää laite ryhmään."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Poista laite ryhmästä."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Avaa sovellus."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Ei yhteyttä."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -335,8 +333,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Syöttölaite"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuulolaitteet"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Otetaan käyttöön…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kirkkautta ei voi säätää, koska ensisijainen sovellus ohjaa sitä"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automaattinen kääntö"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Käännä näyttöä automaattisesti."</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Sijainti"</string>
@@ -428,12 +425,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ympäristö"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vasen"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Oikea"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Laajenna vasemmalle ja oikealle erilliset ohjaimet"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Tiivistä yhtenäiseksi säätimeksi"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mykistä ympäristö"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Poista ympäristön mykistys"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Työkalut"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Livetekstitys"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Muistiinpano"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Kumotaanko laitteen mikrofonin esto?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Kumotaanko laitteen kameran esto?"</string>
@@ -906,7 +911,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Käytä jaettua näyttöä niin, että sovellus on oikealla"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Käytä jaettua näyttöä niin, että sovellus on vasemmalla"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Käytä koko näytön tilaa"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Käytä tietokonenäkymää"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Vaihda sovellukseen oikealla tai alapuolella jaetussa näytössä"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Vaihda sovellukseen vasemmalla tai yläpuolella jaetussa näytössä"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Jaetun näytön aikana: korvaa sovellus toisella"</string>
@@ -993,8 +999,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Näytä vähemmän tärkeät ilmoituskuvakkeet"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ruudun koko päälle/pois"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"poista kiekko"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"lisää laatta viimeiseen kohtaan"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Siirrä kiekkoa"</string>
@@ -1003,6 +1008,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisää paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Virheellinen sijainti."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Paikka <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kiekko lisätty"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kiekko poistettu"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Pika-asetusten muokkausnäkymä"</string>
@@ -1547,10 +1554,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Näet äskeiset sovellukset, kun pyyhkäiset ylös ja pidät kosketuslevyä painettuna kolmella sormella."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Jos haluat nähdä kaikki sovellukset, paina näppäimistön toimintonäppäintä"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Sensuroitu"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Avaa lukitus ja katso tiedot"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Avaa lukitus nähdäksesi koodin"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstuaalinen koulutus"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Takaisin siirtyminen kosketuslevyn avulla"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Pyyhkäise vasemmalle tai oikealle kolmella sormella. Lue lisää eleistä napauttamalla."</string>
@@ -1573,4 +1578,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tuntematon"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Nollataanko kaikki laatat?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Kaikki pika-asetuslaatat palautetaan laitteen alkuperäisiin asetuksiin"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 97702f0847e9..95adcd3e05a4 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Développer le groupe."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Ajouter un appareil au groupe."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Retirer l\'appareil du groupe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Ouvrir l\'appli."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Non connecté"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Itinérance"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Prothèses auditives"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation en cours…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Impossible de régler la luminosité, car elle est contrôlée par l\'appli principale"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation auto"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Localisation"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Environnement"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Gauche"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Droit"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Développer les commandes distinctes à gauche et à droite"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Passer au contrôle unifié"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ignorer les sons de l\'environnement"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Réactiver les sons de l\'environnement"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Outils"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Sous-titres instantanés"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Débloquer le microphone de l\'appareil?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Débloquer l\'appareil photo de l\'appareil?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Utiliser l\'Écran divisé avec l\'appli à droite"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Utiliser l\'Écran divisé avec l\'appli à gauche"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Utiliser le mode plein écran"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Utiliser l\'affichage sur ordinateur de bureau"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Passer à l\'appli à droite ou en dessous avec l\'Écran divisé"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passer à l\'appli à gauche ou au-dessus avec l\'Écran divisé"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"En mode d\'écran divisé : remplacer une appli par une autre"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afficher les icônes de notification de faible priorité"</string>
<string name="other" msgid="429768510980739978">"Autre"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"activer ou désactiver la taille de la tuile"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"retirer la tuile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"Ajouter une tuile à la dernière position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Déplacer la tuile"</string>
@@ -1001,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position incorrecte."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tuile déjà ajoutée"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tuile ajoutée"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tuile retirée"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur de paramètres rapides."</string>
@@ -1545,10 +1551,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Pour afficher les applis récentes, balayez l\'écran vers le haut avec trois doigts sur le pavé tactile et maintenez-les en place"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Pour afficher toutes vos applis, appuyez sur la touche d\'action de votre clavier"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Supprimé"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Déverrouillez pour afficher"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Déverrouillez pour afficher le code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Enseignement contextuel"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Utiliser votre pavé tactile pour revenir en arrière"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Balayez vers la gauche ou vers la droite avec trois doigts. Touchez pour apprendre d\'autres gestes."</string>
@@ -1571,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Inconnu"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Réinitialiser toutes les tuiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Toutes les tuiles des paramètres rapides seront réinitialisées aux paramètres par défaut de l\'appareil."</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
index e9d3c487009a..487c566bf50f 100644
--- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
@@ -68,7 +68,7 @@
</string-array>
<string-array name="tile_states_bt">
<item msgid="5330252067413512277">"Non disponible"</item>
- <item msgid="5315121904534729843">"Désactivée"</item>
+ <item msgid="5315121904534729843">"Désactivé"</item>
<item msgid="503679232285959074">"Activé"</item>
</string-array>
<string-array name="tile_states_airplane">
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 1fefa2df4793..04c147cdd5a7 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Développer le groupe."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Ajouter l\'appareil au groupe."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Supprimer l\'appareil du groupe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Ouvrir l\'application."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Non connecté"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Itinérance"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Appareils auditifs"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Impossible d\'ajuster la luminosité, car celle-ci est contrôlée par l\'appli principale"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation auto"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Localisation"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Sons environnants"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Gauche"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Droite"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Développer les commandes gauche et droite"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Réduire en une commande unifiée"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Couper le mode Sons environnants"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Réactiver le mode Sons environnants"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Outils"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Sous-titres instantanés"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Note"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Débloquer le micro de l\'appareil ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Débloquer la caméra de l\'appareil ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Utiliser l\'écran partagé avec l\'appli sur la droite"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Utiliser l\'écran partagé avec l\'appli sur la gauche"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Utiliser le mode plein écran"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Utiliser l\'affichage sur ordinateur"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Passer à l\'appli à droite ou en dessous avec l\'écran partagé"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passez à l\'appli à gauche ou au-dessus avec l\'écran partagé"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"En mode écran partagé : Remplacer une appli par une autre"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Emplacement non valide."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloc ajouté"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloc supprimé"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur Réglages rapides"</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Inconnu"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Réinitialiser tous les blocs ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tous les blocs \"Réglages rapides\" seront réinitialisés aux paramètres d\'origine de l\'appareil"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index d9a991e29698..6ab779a2569c 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Dispositivo conectado: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Despregar o grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Engadir o dispositivo ao grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Quitar o dispositivo do grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Abrir a aplicación."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Non conectada"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Itinerancia"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiófonos"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Non se pode axustar o brillo porque o controla a aplicación principal"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Xirar automaticamente"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Xirar pantalla automaticamente"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Localización"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Esquerdo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dereito"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Despregar para controis separados do lado esquerdo e do dereito"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Contraer para control unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar o ambiente"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Activar o son ambiental"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Ferramentas"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtítulos instantáneos"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Queres desbloquear o micrófono do dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Queres desbloquear a cámara do dispositivo?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Usar pantalla dividida coa aplicación na dereita"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Usar pantalla dividida coa aplicación na esquerda"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Usar a pantalla completa"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Usar a vista para ordenadores"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Cambiar á aplicación da dereita ou de abaixo coa pantalla dividida"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Cambiar á aplicación da esquerda ou de arriba coa pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"En modo de pantalla dividida: Substituír unha aplicación por outra"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engadir á posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición non válida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Engadiuse a tarxeta"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Quitouse a tarxeta"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configuración rápida."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Categoría descoñecida"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Queres restablecer todos os atallos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Restablecerase a configuración orixinal do dispositivo para todos os atallos de Configuración rápida"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 55f6e947197e..a0dbf73fa73f 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -426,12 +426,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"આસપાસના અવાજો"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ડાબે"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"જમણે"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ડાબે અને જમણે અલગ કરેલા નિયંત્રણો સુધી વિસ્તૃત કરો"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"એકીકૃત નિયંત્રણ સુધી નાનું કરો"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"આસપાસના અવાજો મ્યૂટ કરો"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"આસપાસના અવાજો અનમ્યૂટ કરો"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ટૂલ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"લાઇવ કૅપ્શન"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"નોંધ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ડિવાઇસના માઇક્રોફોનને અનબ્લૉક કરીએ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ડિવાઇસના કૅમેરાને અનબ્લૉક કરીએ?"</string>
@@ -904,7 +912,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"હાલની ઍપને જમણી બાજુએ રાખીને વિભાજિત સ્ક્રીનનો ઉપયોગ કરો"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"હાલની ઍપને ડાબી બાજુએ રાખીને વિભાજિત સ્ક્રીનનો ઉપયોગ કરો"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"પૂર્ણ સ્ક્રીનનો ઉપયોગ કરો"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ડેસ્કટૉપ વ્યૂનો ઉપયોગ કરો"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"વિભાજિત સ્ક્રીનનો ઉપયોગ કરતી વખતે જમણી બાજુ કે નીચેની ઍપ પર સ્વિચ કરો"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"વિભાજિત સ્ક્રીનનો ઉપયોગ કરતી વખતે ડાબી બાજુની કે ઉપરની ઍપ પર સ્વિચ કરો"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"વિભાજિત સ્ક્રીન દરમિયાન: એક ઍપને બીજી ઍપમાં બદલો"</string>
@@ -991,8 +1000,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ઓછી પ્રાધાન્યતાનું નોટિફિકેશન આઇકન બતાવો"</string>
<string name="other" msgid="429768510980739978">"અન્ય"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ટાઇલના કદને ટૉગલ કરો"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ટાઇલ કાઢી નાખો"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"છેલ્લા સ્થાનમાં ટાઇલ ઉમેરો"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ટાઇલ ખસેડો"</string>
@@ -1001,6 +1009,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"જગ્યા પર <xliff:g id="POSITION">%1$d</xliff:g> ઉમેરો"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"સ્થિતિ અમાન્ય છે."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"જગ્યા <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ટાઇલ ઉમેરી"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ટાઇલ કાઢી નાખી"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ઝડપી સેટિંગ એડિટર."</string>
@@ -1545,10 +1555,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"તાજેતરની ઍપ જોવા માટે, ટચપૅડ પર ત્રણ આંગળીઓ વડે ઉપર સ્વાઇપ કરો અને દબાવી રાખો"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"તમારી બધી ઍપ જોવા માટે, તમારા કીબોર્ડ પર ઍક્શન કી દબાવો"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"બદલાવેલું"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"જોવા માટે અનલૉક કરો"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"કોડ જોવા માટે અનલૉક કરો"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"સંદર્ભાત્મક શિક્ષણ"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"પાછા જવા માટે તમારા ટચપૅડનો ઉપયોગ કરો"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ત્રણ આંગળીનો ઉપયોગ કરીને ડાબે અથવા જમણે સ્વાઇપ કરો. સંકેતો વિશે વધુ જાણવા માટે ટૅપ કરો."</string>
@@ -1571,4 +1579,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"અજાણ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"તમામ ટાઇલ રીસેટ કરીએ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"તમામ ઝડપી સેટિંગ ટાઇલને ડિવાઇસના ઑરિજિનલ સેટિંગ પર રીસેટ કરવામાં આવશે"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 8878c1448abf..0b8666115cb6 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> से कनेक्ट किया गया."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> से कनेक्ट है."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ग्रुप को बड़ा करें."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"डिवाइस को ग्रुप में जोड़ें."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"डिवाइस को ग्रुप से हटाएं."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ऐप्लिकेशन खोलें."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"कनेक्ट नहीं है."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"रोमिंग"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"कान की मशीनें"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ब्लूटूथ चालू हो रहा है…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"स्क्रीन की रोशनी को अडजस्ट नहीं किया जा सकता, क्योंकि इसे टॉप ऐप्लिकेशन कंट्रोल कर रहा है"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रीन का अपने-आप दिशा बदलना (ऑटो-रोटेट)"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"जगह की जानकारी"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"आस-पास का वॉल्यूम"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"बाईं ओर के वॉल्यूम के लिए"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"दाईं ओर के वॉल्यूम के लिए"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"दाईं और बाईं ओर के वॉल्यूम को अलग-अलग मैनेज करने के लिए, वॉल्यूम पैनल को बड़ा करें"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"यूनिफ़ाइड कंट्रोल पर जाने के लिए पैनल को छोटा करें"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"आस-पास के वॉल्यूम को म्यूट करें"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"आस-पास के वॉल्यूम को अनम्यूट करें"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"टूल"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"लाइव कैप्शन"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"नोट"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"क्या आपको माइक्रोफ़ोन का ऐक्सेस अनब्लॉक करना है?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"क्या आपको कैमरे का ऐक्सेस अनब्लॉक करना है?"</string>
@@ -606,7 +611,7 @@
<string name="notification_settings_button_description" msgid="2441994740884163889">"सूचना सेटिंग"</string>
<string name="notification_history_button_description" msgid="1578657591405033383">"सूचनाओं का इतिहास"</string>
<string name="notification_section_header_incoming" msgid="850925217908095197">"नई सूचनाएं"</string>
- <string name="notification_section_header_gentle" msgid="6804099527336337197">"साइलेंट मोड में मिली सूचनाएं"</string>
+ <string name="notification_section_header_gentle" msgid="6804099527336337197">"बिना आवाज़ के मिलने वाली सूचनाएं"</string>
<string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचनाएं"</string>
<string name="notification_section_header_conversations" msgid="821834744538345661">"बातचीत"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"बिना आवाज़ की सभी सूचनाएं हटाएं"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"स्प्लिट स्क्रीन की सुविधा चालू करें और इस ऐप्लिकेशन को दाईं ओर दिखाएं"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"स्प्लिट स्क्रीन की सुविधा चालू करें और इस ऐप्लिकेशन को बाईं ओर दिखाएं"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"फ़ुल स्क्रीन मोड का इस्तेमाल करें"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"डेस्कटॉप व्यू मोड का इस्तेमाल करें"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"स्प्लिट स्क्रीन पर, दाईं ओर या नीचे के ऐप पर स्विच करने के लिए"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रीन पर, बाईं ओर या ऊपर के ऐप पर स्विच करने के लिए"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रीन के दौरान: एक ऐप्लिकेशन को दूसरे ऐप्लिकेशन से बदलें"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकता वाली सूचना के आइकॉन दिखाएं"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"टाइल के साइज़ को टॉगल करने के लिए दो बार टैप करें"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाएं"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"आखिरी जगह पर टाइल जोड़ें"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल को किसी और पोज़िशन पर ले जाएं"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर जोड़ें"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"मौजूदा जगह अमान्य है."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"टाइल की पोज़िशन <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल जोड़ी गई"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल हटाई गई"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"त्वरित सेटिंग संपादक."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"हाल ही में इस्तेमाल हुए ऐप देखने के लिए, टचपैड पर तीन उंगलियों से ऊपर की ओर स्वाइप करके दबाकर रखें"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"सभी ऐप्लिकेशन देखने के लिए, कीबोर्ड पर ऐक्शन बटन दबाएं"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"जानकारी छिपाने के लिए सूचना में बदलाव किया गया"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"सार्वजनिक सूचना देखने के लिए डिवाइस अनलॉक करें"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"कोड देखने के लिए अनलॉक करें"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"कॉन्टेक्स्ट के हिसाब से जानकारी"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"वापस जाने के लिए, अपने डिवाइस के टचपैड का इस्तेमाल करें"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"तीन उंगलियों से बाईं या दाईं ओर स्वाइप करें. ज़्यादा जेस्चर के बारे में जानने के लिए टैप करें."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"कोई जानकारी नहीं है"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"क्या सभी टाइल रीसेट करनी हैं?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"क्विक सेटिंग टाइल, डिवाइस की ओरिजनल सेटिंग पर रीसेट हो जाएंगी"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 054f3947ff4d..608b498d2100 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Spojen na <xliff:g id="BLUETOOTH">%s</xliff:g> ."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani ste sa sljedećim uređajem: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Proširite grupu."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodajte uređaj u grupu."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Uklonite uređaj iz grupe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otvorite aplikaciju."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nije povezano."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušna pomagala"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Svjetlina se ne može prilagoditi jer njome upravlja aplikacija pri vrhu"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko zakretanje"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okruženje"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lijevo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Proširi u zasebne kontrole slijeva i zdesna"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sažmi u objedinjenu kontrolu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Isključi zvuk okruženja"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Uključi zvuk okruženja"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Alati"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Automatski titlovi"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Napomena"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite li deblokirati mikrofon uređaja?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Želite li deblokirati kameru uređaja?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Upotreba podijeljenog zaslona s aplikacijom s desne strane"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Upotreba podijeljenog zaslona s aplikacijom s lijeve strane"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Upotreba prikaza na cijelom zaslonu"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Upotreba prikaza na računalu"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Prelazak na aplikaciju zdesna ili ispod uz podijeljeni zaslon"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Prelazak na aplikaciju slijeva ili iznad uz podijeljeni zaslon"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Tijekom podijeljenog zaslona: zamijeni aplikaciju drugom"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj nije važeći."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivač brzih postavki."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite li poništiti sve pločice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve pločice Brze postavke vratit će se na izvorne postavke uređaja"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 03f1bc6407d8..36187b97d70b 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Csatlakoztatva a következőhöz: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Csatlakozva a következőhöz: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Csoport kibontása."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Eszköz hozzáadása csoporthoz."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Eszköz eltávolítása csoportból."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Alkalmazás megnyitása."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nincs csatlakozva."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Bevitel"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hallókészülék"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Bekapcsolás…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nem lehet módosítani a fényerőt, mert a felső alkalmazás vezérli"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatikus elforgatás"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatikus képernyőforgatás"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Tartózkodási hely"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Környezet"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Bal"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Jobb"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Kibontás a balra és jobbra elválasztott vezérlőkhöz"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Összecsukás az egységes vezérléshez"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Környezet némítása"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Környezet némításának feloldása"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Eszközök"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Élő feliratozás"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Megjegyzés"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Feloldja az eszköz mikrofonjának letiltását?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Feloldja az eszköz kamerájának letiltását?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Osztott képernyő használata, az alkalmazás a jobb oldalon van"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Osztott képernyő használata, az alkalmazás a bal oldalon van"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Teljes képernyő használata"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Asztali nézet használata"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Váltás a jobb oldalt, illetve lent lévő appra osztott képernyő esetén"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Váltás a bal oldalt, illetve fent lévő appra osztott képernyő esetén"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Osztott képernyőn: az egyik alkalmazás lecserélése egy másikra"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Alacsony prioritású értesítési ikonok mutatása"</string>
<string name="other" msgid="429768510980739978">"Egyéb"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"a mozaik méretének módosítása"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"mozaik eltávolításához"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"mozaik hozzáadása az utolsó pozícióhoz"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mozaik áthelyezése"</string>
@@ -1001,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Hozzáadás a következő pozícióhoz: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Érvénytelen pozíció."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. hely"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"A kártya már hozzá van adva"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kártya hozzáadva"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kártya eltávolítva"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Gyorsbeállítások szerkesztője"</string>
@@ -1545,10 +1551,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"A legutóbbi appokért csúsztasson lefelé három ujjal az érintőpadon, és tartsa lenyomva ujjait."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Az összes alkalmazás megtekintéséhez nyomja meg a billentyűzet műveletbillentyűjét."</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Törölve"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Oldja fel a megtekintéshez"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kód a megtekintéshez való feloldáshoz"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextusfüggő tájékoztató párbeszédpanel"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"A visszalépéshez használja az érintőpadot"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Csúsztatasson gyorsan három ujjal balra vagy jobbra. Koppintson a további kézmozdulatokért."</string>
@@ -1571,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ismeretlen"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Visszaállítja az összes mozaikot?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Az összes Gyorsbeállítások mozaik visszaáll az eszköz eredeti beállításaira"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index e1b6639b2889..6a7543f754fd 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Միացված է <xliff:g id="BLUETOOTH">%s</xliff:g>-ին:"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Միացված է <xliff:g id="CAST">%s</xliff:g>-ին:"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Ծավալել խումբը։"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Սարքը ավելացնել խմբին։"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Հեռացնել սարքը խմբից։"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Բացել հավելվածը։"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Միացված չէ:"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Ռոումինգ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Մուտքագրում"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Լսողական սարք"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Միացում…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Հնարավոր չէ կարգավորել պայծառությունը, քանի որ այն կառավարվում է գլխավոր հավելվածի կողմից"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ինքնապտտում"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ավտոմատ պտտել էկրանը"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Տեղորոշում"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Շրջակայք"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ձախ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Աջ"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Շրջակայք"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Ձախ շրջակայք"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Աջ շրջակայք"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Ծավալել՝ դարձնելով կառավարման աջ և ձախ առանձնացված տարրեր"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Ծալել՝ դարձնելով կառավարման մեկ միասնական տարր"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Անջատել շրջակայքի ձայները"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Միացնել շրջակայքի ձայները"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Գործիքներ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Կենդանի ենթագրեր"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Նշում"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Արգելահանե՞լ սարքի խոսափողը"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Արգելահանե՞լ սարքի տեսախցիկը"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Տրոհել էկրանը և տեղավորել այս հավելվածը աջ կողմում"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Տրոհել էկրանը և տեղավորել այս հավելվածը ձախ կողմում"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Օգտագործեք լիաէկրան ռեժիմը"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Օգտագործեք համակարգչային տարբերակը"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Անցեք աջ կողմի կամ ներքևի հավելվածին տրոհված էկրանի միջոցով"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Անցեք աջ կողմի կամ վերևի հավելվածին տրոհված էկրանի միջոցով"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Տրոհված էկրանի ռեժիմում մեկ հավելվածը փոխարինել մյուսով"</string>
@@ -1000,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ավելացնել դիրք <xliff:g id="POSITION">%1$d</xliff:g>-ում"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Դիրքն անվավեր է։"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Սալիկն արդեն ավելացվել է"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Սալիկն ավելացվեց"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Սալիկը հեռացվեց"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Արագ կարգավորումների խմբագրիչ:"</string>
@@ -1568,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Անհայտ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Զրոյացնե՞լ բոլոր սալիկները"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Արագ կարգավորումների բոլոր սալիկները կզրոյացվեն սարքի սկզբնական կարգավորումների համաձայն։"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 6ef3cdc65e69..c09e03c428d2 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Terhubung ke <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Terhubung ke <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Luaskan grup."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Tambahkan perangkat ke grup."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Hapus perangkat dari grup."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Buka aplikasi."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Tidak terhubung."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu dengar"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Mengaktifkan…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Tidak dapat menyesuaikan kecerahan karena sedang dikontrol oleh aplikasi atas"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Putar Otomatis"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Putar layar otomatis"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasi"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Suara sekitar"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kiri"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kanan"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Luaskan ke kontrol terpisah kiri dan kanan"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Ciutkan ke kontrol terpadu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Bisukan suara sekitar"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Bunyikan suara sekitar"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Alat"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Teks Otomatis"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Catatan"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Berhenti memblokir mikrofon perangkat?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Berhenti memblokir kamera perangkat?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Gunakan layar terpisah dengan aplikasi di sebelah kanan"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Gunakan layar terpisah dengan aplikasi di sebelah kiri"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Gunakan layar penuh"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Gunakan tampilan desktop"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Beralih ke aplikasi di bagian kanan atau bawah saat menggunakan layar terpisah"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Beralih ke aplikasi di bagian kiri atau atas saat menggunakan layar terpisah"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Dalam layar terpisah: ganti salah satu aplikasi dengan yang lain"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Tampilkan ikon notifikasi prioritas rendah"</string>
<string name="other" msgid="429768510980739978">"Lainnya"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"mengubah ukuran kartu"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"menghapus kartu"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"menambahkan kartu ke posisi terakhir"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pindahkan kartu"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan ke posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisi tidak valid."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartu ditambahkan"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartu dihapus"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor setelan cepat."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Untuk melihat aplikasi terkini, geser ke atas dan tahan menggunakan tiga jari di touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Untuk melihat semua aplikasi, tekan tombol tindakan di keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Disamarkan"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Buka kunci untuk melihat"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Buka kunci untuk melihat kode"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Pendidikan kontekstual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Gunakan touchpad untuk kembali"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Geser ke kiri atau kanan menggunakan tiga jari. Ketuk untuk mempelajari gestur lainnya."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tidak diketahui"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset semua kartu?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Semua kartu Setelan Cepat akan direset ke setelan asli perangkat"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 2b8a5cc51e87..b853a5561c4c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tengt við <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Tengt við <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Stækka hóp."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Bæta tæki við hóp."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Fjarlægja tæki úr hóp."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Opna forrit."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Engin tenging."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Reiki"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Inntak"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Heyrnartæki"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Kveikir…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ekki er hægt að breyta birtustiginu vegna þess að efsta forritið stjórnar því"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Sjálfvirkur snúningur"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Snúa skjá sjálfkrafa"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Staðsetning"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Umhverfi"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vinstri"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Hægri"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Umhverfi"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Umhverfi til vinstri"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Umhverfi til hægri"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Aðskilja stýringar til vinstri og hægri"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Taka saman í eina stýringu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Þagga umhverfi"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Kveikja á hljóði umhverfis"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Verkfæri"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Skjátextar í rauntíma"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Glósa"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Opna fyrir hljóðnema tækisins?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Opna fyrir myndavél tækisins?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Notaðu skjáskiptingu fyrir forritið til hægri"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Notaðu skjáskiptingu fyrir forritið til vinstri"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Nota allan skjáinn"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Nota tölvuútgáfu"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Skiptu í forrit til hægri eða fyrir neðan þegar skjáskipting er notuð"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skiptu í forrit til vinstri eða fyrir ofan þegar skjáskipting er notuð"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Í skjáskiptingu: Skipta forriti út fyrir annað forrit"</string>
@@ -1000,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bæta við í stöðu <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Staða ógild."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Staða <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Skífu hefur þegar verið bætt við"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Reit bætt við"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Reitur fjarlægður"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Flýtistillingaritill."</string>
@@ -1568,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Óþekkt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Endurstilla alla reiti?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Allir flýtistillingareitir munu endurstillast á upprunalegar stillingar tækisins"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 87507d6297e5..ceb1ca1c5cb4 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connesso a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connesso a: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Espandi il gruppo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Aggiungi il dispositivo al gruppo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Rimuovi il dispositivo dal gruppo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Apri l\'applicazione."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Non connesso."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingresso"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Apparecchi acustici"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Attivazione…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Impossibile regolare la luminosità perché è controllata dall\'app in primo piano."</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotazione automatica"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotazione automatica dello schermo"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Posizione"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Audio ambientale"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Sinistra"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Destra"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Audio ambientale"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Audio ambientale a sinistra"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Audio ambientale a destra"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Espandi controlli separati a sinistra e a destra"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Comprimi in controllo unificato"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Disattiva audio ambientale"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Riattiva audio ambientale"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Strumenti"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Sottotitoli in tempo reale"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vuoi sbloccare il microfono del dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vuoi sbloccare la fotocamera del dispositivo?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Utilizza lo schermo diviso con l\'app a destra"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Utilizza lo schermo diviso con l\'app a sinistra"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Utilizza la modalità a schermo intero"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Utilizza la visualizzazione desktop"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Passa all\'app a destra o sotto mentre usi lo schermo diviso"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passa all\'app a sinistra o sopra mentre usi lo schermo diviso"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Con lo schermo diviso: sostituisci un\'app con un\'altra"</string>
@@ -1000,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Aggiungi alla posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posizione non valida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Riquadro già aggiunto"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Riquadro aggiunto"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Riquadro rimosso"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor di impostazioni rapide."</string>
@@ -1568,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Sconosciuti"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reimpostare tutti i riquadri?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tutti i riquadri Impostazioni rapide verranno reimpostati sulle impostazioni originali del dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 8a687af124e0..8123c7e2ea38 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -245,20 +245,15 @@
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‏Bluetooth מחובר."</string>
<string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"‏סמל של מכשיר Bluetooth"</string>
<string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"יש ללחוץ כדי להגדיר את פרטי המכשיר"</string>
- <!-- no translation found for accessibility_bluetooth_device_settings_gear_with_name (114373701123165491) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_see_all (5260390270128256620) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (7988547106800504256) -->
- <skip />
+ <string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"‫<xliff:g id="DEVICE_NAME">%s</xliff:g>. הגדרה של פרטי המכשיר"</string>
+ <string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"הצגת כל המכשירים"</string>
+ <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"התאמה של מכשיר חדש"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"התבצע חיבור אל <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"מחובר אל <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"הרחבת הקבוצה."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"הוספת מכשיר לקבוצה"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"הסרת מכשיר מקבוצה"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"פתיחת האפליקציה."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"אין חיבור."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"נדידה"</string>
@@ -336,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"קלט"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"מכשירי שמיעה"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ההפעלה מתבצעת…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"אי אפשר לשנות את הבהירות כי היא נקבעת על ידי האפליקציה העליונה"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"סיבוב אוטומטי"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"סיבוב אוטומטי של המסך"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"מיקום"</string>
@@ -354,7 +348,7 @@
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"אין רשתות זמינות"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"‏אין רשתות Wi-Fi זמינות"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"בתהליך הפעלה…"</string>
- <string name="quick_settings_cast_title" msgid="3033553249449938182">"‏העברה (cast)"</string>
+ <string name="quick_settings_cast_title" msgid="3033553249449938182">"‏הפעלת Cast"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"‏מופעל Cast"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"מכשיר ללא שם"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"אין מכשירים זמינים"</string>
@@ -429,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"הרעשים בסביבה"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"שמאל"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ימין"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"הרחבה לאמצעי בקרה נפרדים לצד שמאל ולצד ימין"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"כיווץ לאמצעי בקרה מאוחד"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"השתקת הרעשים בסביבה"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"ביטול השתקת הרעשים בסביבה"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"כלים"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"כתוביות מיידיות"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"פתק"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"לבטל את חסימת המיקרופון של המכשיר?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"לבטל את חסימת המצלמה של המכשיר?"</string>
@@ -589,7 +591,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"‏הפעלת Cast של אפליקציה אחת"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"‏הפעלת Cast של כל המסך"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"‏כשמפעילים Cast של כל המסך, כל מה שמופיע בו יהיה גלוי לצופים. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"‏כשמפעילים Cast של כל אפליקציה, כל מה שמופיע או מופעל בה יהיה גלוי לצופים. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"‏כשמפעילים Cast של אפליקציה, כל מה שרואים או מפעילים בה מופיע גם לצופים. מומלץ להיזהר ולא לחשוף פרטים אישיים כמו סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"‏הפעלת Cast של המסך"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"‏בחירת אפליקציה להפעלת Cast"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"להתחיל את השיתוף?"</string>
@@ -906,9 +908,8 @@
<string name="keyboard_shortcut_group_system_multitasking" msgid="6967816258924795558">"ריבוי משימות"</string>
<string name="system_multitasking_rhs" msgid="8779289852395243004">"שימוש במסך מפוצל כשהאפליקציה בצד ימין"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"שימוש במסך מפוצל כשהאפליקציה בצד שמאל"</string>
- <!-- no translation found for system_multitasking_full_screen (4221409316059910349) -->
- <skip />
- <!-- no translation found for system_multitasking_desktop_view (8829838918507805921) -->
+ <string name="system_multitasking_full_screen" msgid="4221409316059910349">"שימוש במסך מלא"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
<skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"מעבר לאפליקציה משמאל או למטה בזמן שימוש במסך מפוצל"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"מעבר לאפליקציה מימין או למעלה בזמן שימוש במסך מפוצל"</string>
@@ -996,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"הצגת סמלי התראות בעדיפות נמוכה"</string>
<string name="other" msgid="429768510980739978">"אחר"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"שינוי גודל הלחצן"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"הסרת הלחצן"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"הוספת הלחצן במיקום האחרון"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"העברת הלחצן"</string>
@@ -1006,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"הוספה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"המיקום לא תקין."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"מיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"הלחצן נוסף"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"הלחצן הוסר"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"עורך הגדרות מהירות."</string>
@@ -1512,8 +1514,7 @@
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"הצגת האפליקציות האחרונות"</string>
<string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"מעבר בין אפליקציות"</string>
<string name="touchpad_tutorial_done_button" msgid="176168488821755503">"סיום"</string>
- <!-- no translation found for touchpad_tutorial_next_button (9169718126626806688) -->
- <skip />
+ <string name="touchpad_tutorial_next_button" msgid="9169718126626806688">"הבא"</string>
<string name="gesture_error_title" msgid="469064941635578511">"צריך לנסות שוב."</string>
<string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"חזרה"</string>
<string name="touchpad_back_gesture_guidance" msgid="5352221087725906542">"מחליקים שמאלה או ימינה עם שלוש אצבעות על לוח המגע"</string>
@@ -1551,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"כדי לראות את האפליקציות האחרונות, מחליקים למעלה לוחצים לחיצה ארוכה עם שלוש אצבעות על לוח המגע"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"כדי לראות את כל האפליקציות, מקישים על מקש הפעולה במקלדת"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"מצונזר"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"צריך לפתוח את הנעילה כדי לראות"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"כדי לראות את הקוד, צריך לפתוח את הנעילה"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"חינוך בהתאם להקשר"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"אפשר להשתמש בלוח המגע כדי לחזור אחורה"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"מחליקים ימינה או שמאלה עם שלוש אצבעות. ניתן ללחוץ כדי לקבל מידע נוסף על התנועות."</string>
@@ -1577,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"לא ידוע"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"לאפס את כל הלחצנים?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"כל הלחצנים ב\'הגדרות מהירות\' יאופסו להגדרות המקוריות של המכשיר"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"‫<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 29f93cc8c361..2fc703c44ca2 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>に接続しました。"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>に接続されています。"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"グループを開きます。"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"グループにデバイスを追加します。"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"グループからデバイスを削除します。"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"アプリを開きます。"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"接続されていません。"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ローミング"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"入力"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"補聴器"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ON にしています…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"明るさはトップ アプリによって制御されているため、調整できません"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動回転"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"画面を自動回転します"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"位置情報"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"周囲の音"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"周囲の音"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"周囲の音(左)"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"周囲の音(右)"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"開く - 左右それぞれで制御する"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"閉じる - まとめて制御する"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"周囲の音をミュート"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"周囲の音のミュートを解除"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ツール"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"自動字幕起こし"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"設定"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"注"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"デバイスのマイクのブロックを解除しますか?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"デバイスのカメラのブロックを解除しますか?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"分割画面の使用(アプリを右側に表示)"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"分割画面の使用(アプリを左側に表示)"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"全画面表示に切り替える"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"デスクトップ ビューを使用する"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"分割画面の使用時に右側または下部のアプリに切り替える"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"分割画面の使用時に左側または上部のアプリに切り替える"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"分割画面中: アプリを順に置換する"</string>
@@ -1000,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ポジション <xliff:g id="POSITION">%1$d</xliff:g> に追加"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置が無効です。"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"タイルはすでに追加されています"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"タイルを追加しました"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"タイルを削除しました"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"クイック設定エディタ"</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"すべてのタイルをリセットしますか?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"すべてのクイック設定タイルがデバイスの元の設定にリセットされます"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>、<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d9a5824653c1..86f8e33b2d76 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"დაკავშირებულია <xliff:g id="BLUETOOTH">%s</xliff:g>-თან."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"დაკავშირებულია მოწყობილობასთან: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ჯგუფის გაფართოება."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"მოწყობილობის ჯგუფში დამატება."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"მოწყობილობის ჯგუფიდან ამოშლა."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"აპლიკაციის გახსნა."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"არ არის დაკავშირებული."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"როუმინგი"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"შეყვანა"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"სმენის მოწყობილობები"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ირთვება…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"სიკაშკაშის კორექტირება ვერ ხერხდება, რადგან ის იმართება გახსნილი აპის მიერ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ავტოროტაცია"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ეკრანის ავტომატური შეტრიალება"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"მდებარეობა"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"გარემოცვა"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"მარცხენა"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"მარჯვენა"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"გარემოცვა"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"მარცხენა გარემოცვა"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"მარჯვენა გარემოცვა"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"განცალკევებული მართვის საშუალებების გაფართოება მარცხნივ და მარჯვნივ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ერთიანი მართვის ჩაკეცვა"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"გარემოცვის დადუმება"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"გარემოცვის დადუმების მოხსნა"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ხელსაწყოები"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"ავტოსუბტიტრები"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"პარამეტრები"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ჩანიშვნა"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"გსურთ მოწყობილობის მიკროფონის განბლოკვა?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"გსურთ მოწყობილობის კამერის განბლოკვა?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ეკრანის გაყოფის გამოყენება აპზე მარჯვნივ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ეკრანის გაყოფის გამოყენება აპზე მარცხნივ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"სრული ეკრანის გამოყენება"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"დესკტოპის ხედის გამოყენება"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"ეკრანის გაყოფის გამოყენებისას აპზე მარჯვნივ ან ქვემოთ გადართვა"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ეკრანის გაყოფის გამოყენებისას აპზე მარცხნივ ან ზემოთ გადართვა"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ეკრანის გაყოფის დროს: ერთი აპის მეორით ჩანაცვლება"</string>
@@ -1000,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"დამატება პოზიციაზე <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"პოზიცია არასწორია."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"პოზიცია <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"მოზაიკის ფილა უკვე დამატებულია"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"მოზაიკის ფილა დაემატა"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"მოზაიკის ფილა ამოიშალა"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"სწრაფი პარამეტრების რედაქტორი."</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"უცნობი"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"გსურთ ყველა ფილის გადაყენება?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"სწრაფი პარამეტრების ყველა ფილა გადაყენდება მოწყობილობის ორიგინალ პარამეტრებზე"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 141e99c10854..1a7532b938d7 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -147,7 +147,7 @@
<string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"Қазір қолданбамен бөлісіп жатырсыз."</string>
<string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Бөлісуді тоқтату"</string>
<string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"Экранды трансляциялап жатырсыз."</string>
- <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Трансляциялау тоқтасын ба?"</string>
+ <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Трансляцияны тоқтату керек пе?"</string>
<string name="cast_to_other_device_stop_dialog_message_entire_screen_with_device" msgid="1474703115926205251">"Қазір бүкіл экранды құрылғыға (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) трансляциялап жатырсыз."</string>
<string name="cast_to_other_device_stop_dialog_message_entire_screen" msgid="8419219169553867625">"Қазір бүкіл экранды маңайдағы құрылғыға трансляциялап жатырсыз."</string>
<string name="cast_to_other_device_stop_dialog_message_specific_app_with_device" msgid="2715934698604085519">"Қазір қолданбаны (<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>) құрылғыға (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) трансляциялап жатырсыз."</string>
@@ -247,15 +247,13 @@
<string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Құрылғы деректерін конфигурациялау үшін басыңыз."</string>
<string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"<xliff:g id="DEVICE_NAME">%s</xliff:g>. Құрылғы мәліметтерін конфигурациялау"</string>
<string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"Барлық құрылғыны көру"</string>
- <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"Жаңа құрылғыны жұптау"</string>
+ <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"Жаңа құрылғымен жұптау"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> трансляциясына қосылды."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Топты жайыңыз."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Құрылғыны топқа қосады."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Құрылғыны топтан өшіреді."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Қолданбаны ашыңыз."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Жалғанбаған."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string>
@@ -308,7 +306,7 @@
<string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
<string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Жұптасқан құрылғылар жоқ"</string>
<string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Құрылғыны жалғау не ажырату үшін түртіңіз."</string>
- <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Жаңа құрылғыны жұптау"</string>
+ <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Жаңа құрылғымен жұптау"</string>
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Барлығын көру"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ты пайдалану"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Қосылды"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Кіріс"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Есту аппараттары"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Қосылып жатыр…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Жарықтықты реттеу мүмкін емес, себебі ол жетекші қолданба арқылы басқарылады."</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматты түрде бұру"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматты айналатын экран"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Локация"</string>
@@ -418,7 +415,7 @@
<string name="quick_settings_hearing_devices_connected" msgid="6519069502397037781">"Қосулы"</string>
<string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Ажыратулы"</string>
<string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Есту құрылғылары"</string>
- <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Жаңа құрылғыны жұптау"</string>
+ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Жаңа құрылғымен жұптау"</string>
<string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Жаңа құрылғыны жұптау үшін басыңыз."</string>
<string name="hearing_devices_presets_error" msgid="350363093458408536">"Параметрлер жинағын жаңарту мүмкін болмады."</string>
<string name="hearing_devices_preset_label" msgid="7878267405046232358">"Параметрлер жинағы"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Айнала"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Сол жақ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Оң жақ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Сол жақ және оң жақ бөлек бақылау құралдарына жаю"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Біріктірілген бақылау құралына жию"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Айналаның дыбысын өшіру"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Айналаның дыбысын қосу"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Құралдар"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live Caption"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Ескертпе"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Құрылғы микрофонын блоктан шығару керек пе?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Құрылғы камерасын блоктан шығару керек пе?"</string>
@@ -586,7 +591,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"Бір қолданба экранын трансляциялау"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"Бүкіл экранды трансляциялау"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Бүкіл экранды трансляциялаған кезде экранда барлық нәрсе көрсетіледі. Сондықтан құпия сөздерге, төлем туралы мәліметке, хабарларға, фотосуреттерге, аудиоконтент пен бейнелерге сақ болыңыз."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Қолданба экранын трансляциялаған кезде қолданбадағы барлық контент көрсетіледі. Сондықтан құпия сөздерге, төлем туралы мәліметке, хабарларға, фотосуреттерге, аудиоконтент пен бейнелерге сақ болыңыз."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Қолданба экранын трансляциялаған кезде, қолданбадағы барлық контент көрсетіледі. Сондықтан құпия сөздерге, төлем туралы мәліметке, хабарларға, фотосуреттерге, аудиоконтент пен бейнелерге сақ болыңыз."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Экранды трансляциялау"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Трансляциялайтын қолданба экранын таңдау"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Бөлісу басталсын ба?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Қолданбаны бөлінген экранның оң жағынан пайдалану"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Қолданбаны бөлінген экранның сол жағынан пайдалану"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Толық экранды пайдалану"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Компьютерлік нұсқаны пайдалану"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Бөлінген экранда оң не төмен жақтағы қолданбаға ауысу"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Бөлінген экранда сол не жоғары жақтағы қолданбаға ауысу"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Экранды бөлу кезінде: бір қолданбаны басқасымен алмастыру"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Маңызды емес хабарландыру белгішелерін көрсету"</string>
<string name="other" msgid="429768510980739978">"Басқа"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"бөлшек өлшемін ауыстыру"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"бөлшекті өшіру"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"соңғы позицияға бөлшек қосу"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Бөлшекті жылжыту"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> орнына қосу"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Орын жарамсыз."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> орны"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Бөлшек қосылды."</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Бөлшек өшірілді."</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Жылдам параметрлер өңдегіші."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Сенсорлық тақтада үш саусақпен жоғары сырғытып, басып тұрсаңыз, соңғы ашылған қолданбаларды көресіз."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Пернетақтада әрекет пернесін басып, барлық қолданбаны көре аласыз."</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Жасырылған"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Көру үшін құлыпты ашыңыз."</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Кодты көру үшін құлыпты ашыңыз."</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контекстік білім"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Артқа қайту үшін сенсорлық тақтаны қолданыңыз"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Үш саусақпен солға не оңға сырғытыңыз. Басқа қимылдарды үйрену үшін түртіңіз."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Белгісіз"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Барлық бөлшекті бастапқы күйге қайтару керек пе?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Барлық \"Жылдам параметрлер\" бөлшегі құрылғының бастапқы параметрлеріне қайтарылады."</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9323308b67b6..43c5c700d206 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"បាន​ភ្ជាប់​ទៅ <xliff:g id="BLUETOOTH">%s</xliff:g> ។"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"បានភ្ជាប់ទៅ <xliff:g id="CAST">%s</xliff:g>"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ពង្រីកក្រុម។"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"បញ្ចូលឧបករណ៍ទៅក្រុម។"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ដកឧបករណ៍ចេញពីក្រុម។"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"បើកកម្មវិធី។"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"មិន​បាន​តភ្ជាប់​។"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"រ៉ូ​មីង"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"បញ្ចូល"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"កំពុង​បើក..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"មិនអាចកែតម្រូវកម្រិតពន្លឺបានទេ ដោយសារវាកំពុងស្ថិតក្រោមការគ្រប់គ្រងរបស់កម្មវិធីខាងលើគេ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"បង្វិល​ស្វ័យ​ប្រវត្តិ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"បង្វិលអេក្រង់ស្វ័យប្រវត្តិ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ទី​តាំង​"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"មជ្ឈដ្ឋានជុំវិញ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ឆ្វេង"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ស្ដាំ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ពង្រីកទៅជាការគ្រប់គ្រងខាងឆ្វេង និងខាងស្ដាំដាច់ដោយឡែកពីគ្នា"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"បង្រួមទៅជាការគ្រប់គ្រងដែលបានរួមបញ្ចូលគ្នា"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"បិទសំឡេងមជ្ឈដ្ឋានជុំវិញ"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"បើកសំឡេងមជ្ឈដ្ឋានជុំវិញ"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ឧបករណ៍"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"អក្សររត់ក្នុងពេលជាក់ស្ដែង"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"កំណត់ចំណាំ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ឈប់ទប់ស្កាត់​មីក្រូហ្វូន​របស់ឧបករណ៍ឬ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ឈប់ទប់ស្កាត់​កាមេរ៉ា​របស់ឧបករណ៍ឬ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ប្រើមុខងារបំបែកអេក្រង់ជាមួយកម្មវិធីនៅខាងស្ដាំ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ប្រើមុខងារបំបែកអេក្រង់ជាមួយកម្មវិធីនៅខាងឆ្វេង"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ប្រើអេក្រង់ពេញ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ប្រើទិដ្ឋភាព​លើកុំព្យូទ័រ"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"ប្ដូរទៅកម្មវិធីនៅខាងស្ដាំ ឬខាងក្រោម ពេលកំពុងប្រើមុខងារ​បំបែកអេក្រង់"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ប្ដូរទៅកម្មវិធីនៅខាងឆ្វេង ឬខាងលើ ពេលកំពុងប្រើមុខងារ​បំបែកអេក្រង់"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ក្នុងអំឡុងពេលប្រើមុខងារបំបែកអេក្រង់៖ ជំនួសកម្មវិធីពីមួយទៅមួយទៀត"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"បញ្ចូលទៅ​ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ទីតាំងគ្មានសុពលភាព។"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"បានបញ្ចូលប្រអប់"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"បាន​ផ្លាស់ទី​ប្រអប់"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"កម្មវិធីកែការកំណត់រហ័ស"</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"មិនស្គាល់"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"កំណត់ប្រអប់ទាំងអស់​ឡើងវិញឬ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ប្រអប់​ការកំណត់រហ័សទាំងអស់នឹងកំណត់ឡើងវិញទៅការ​កំណត់ដើមរបស់ឧបករណ៍"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index d7f8b89878f9..8de5281e0a3f 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ಗುಂಪು ವಿಸ್ತರಿಸಿ."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ಸಾಧನವನ್ನು ಗುಂಪಿಗೆ ಸೇರಿಸಿ."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ಸಾಧನವನ್ನು ಗುಂಪಿನಿಂದ ತೆಗೆದುಹಾಕಿ."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ಅಪ್ಲಿಕೇಶನ್ ತೆರೆಯಿರಿ."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ರೋಮಿಂಗ್"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ಇನ್‌ಪುಟ್"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ಆನ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ಬ್ರೈಟ್‌ನೆಸ್ ಅನ್ನು ಹೊಂದಾಣಿಕೆ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ, ಏಕೆಂದರೆ ಅದನ್ನು ಟಾಪ್ ಆ್ಯಪ್ ನಿಯಂತ್ರಿಸುತ್ತಿದೆ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ಸ್ವಯಂ-ತಿರುಗುವಿಕೆ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ಸ್ಥಳ"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ಎಡ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ಬಲ"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"ಎಡಭಾಗದ ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"ಬಲಭಾಗದ ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ಪ್ರತ್ಯೇಕ ಎಡ ಮತ್ತು ಬಲ ಕಂಟ್ರೋಲ್‌ಗಳಿಗಾಗಿ ವಿಸ್ತರಿಸಿ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ಯೂನಿಫೈಡ್ ಕಂಟ್ರೋಲ್‌ಗಾಗಿ ಕುಗ್ಗಿಸಿ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್ ಅನ್ನು ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್ ಅನ್ನು ಅನ್‌ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ಟೂಲ್‌ಗಳು"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"ಲೈವ್ ಕ್ಯಾಪ್ಶನ್"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ಟಿಪ್ಪಣಿ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ಸಾಧನದ ಮೈಕ್ರೋಫೋನ್ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಬೇಕೆ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ಸಾಧನದ ಕ್ಯಾಮರಾ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಬೇಕೆ?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ಬಲಭಾಗದಲ್ಲಿ ಆ್ಯಪ್ ಮೂಲಕ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಬಳಸಿ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ಎಡಭಾಗದಲ್ಲಿ ಆ್ಯಪ್ ಮೂಲಕ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಬಳಸಿ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ಫುಲ್‌ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬಳಸಿ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ಡೆಸ್ಕ್‌ಟಾಪ್ ವೀಕ್ಷಣೆಯನ್ನು ಬಳಸಿ"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸಿ ಮೋಡ್ ಬಳಸುವಾಗ ಬಲಭಾಗ ಅಥವಾ ಕೆಳಭಾಗದಲ್ಲಿರುವ ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸಿ ಮೋಡ್ ಬಳಸುವಾಗ ಎಡಭಾಗ ಅಥವಾ ಮೇಲ್ಭಾಗದಲ್ಲಿರುವ ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸುವ ಸಮಯದಲ್ಲಿ: ಒಂದು ಆ್ಯಪ್‌ನಿಂದ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
@@ -1000,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸೇರಿಸಿ"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ಸ್ಥಾನವು ಅಮಾನ್ಯವಾಗಿದೆ."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ಸ್ಥಾನ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"ಟೈಲ್ ಅನ್ನು ಈಗಾಗಲೇ ಸೇರಿಸಲಾಗಿದೆ"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ಟೈಲ್ ಸೇರಿಸಲಾಗಿದೆ"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ಟೈಲ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‍ಗಳ ಎಡಿಟರ್."</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ಅಪರಿಚಿತ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ಎಲ್ಲಾ ಟೈಲ್‌ಗಳನ್ನು ರೀಸೆಟ್ ಮಾಡಬೇಕೆ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ಎಲ್ಲಾ ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಟೈಲ್‌ಗಳನ್ನು ಸಾಧನದ ಮೂಲ ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ರೀಸೆಟ್ ಮಾಡಲಾಗುತ್ತದೆ"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 0c43ea1de5c2..c3a1a1a63bc5 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>에 연결되었습니다."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>에 연결됨"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"그룹을 펼칩니다."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"기기를 그룹에 추가합니다."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"그룹에서 기기를 삭제합니다."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"애플리케이션을 엽니다."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"연결되지 않았습니다."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"로밍"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"입력"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"보청기"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"켜는 중..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"상위 앱에서 밝기를 제어하고 있으므로 밝기를 조절할 수 없습니다."</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"자동 회전"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"화면 자동 회전"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"위치"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"주변 소리"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"왼쪽"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"오른쪽"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"왼쪽 및 오른쪽 개별 제어로 확장"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"통합 제어로 축소"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"주변 소리 음소거"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"주변 소리 음소거 해제"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"도구"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"실시간 자막"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"메모"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"기기 마이크를 차단 해제하시겠습니까?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"기기 카메라를 차단 해제하시겠습니까?"</string>
@@ -586,7 +591,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"앱 1개 전송"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"전체 화면 전송"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"전체 화면을 전송하면 화면에 있는 모든 항목을 볼 수 있게 됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"앱을 전송하면 해당 앱에 표시되거나 재생되는 모든 항목을 볼 수 있게 됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"앱을 전송하면 해당 앱에 표시되거나 재생되는 모든 항목을 볼 수 있게 됩니다. 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"화면 전송"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"전송할 앱 선택"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"공유를 시작하시겠습니까?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"앱이 오른쪽에 오도록 화면 분할 사용"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"앱이 왼쪽에 오도록 화면 분할 사용"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"전체 화면 사용"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"데스크톱 뷰 사용"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"화면 분할을 사용하는 중에 오른쪽 또는 아래쪽에 있는 앱으로 전환"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"화면 분할을 사용하는 중에 왼쪽 또는 위쪽에 있는 앱으로 전환하기"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"화면 분할 중: 다른 앱으로 바꾸기"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"우선순위가 낮은 알림 아이콘 표시"</string>
<string name="other" msgid="429768510980739978">"기타"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"타일 크기 전환"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"타일 삭제"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"마지막 위치에 타일 추가"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"타일 이동"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> 위치에 추가"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"위치가 잘못되었습니다."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> 위치"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"타일 추가됨"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"타일 삭제됨"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"빠른 설정 편집기"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"최근 앱을 보려면 터치패드에서 세 손가락으로 위로 스와이프한 후 잠시 기다리세요"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"모든 앱을 보려면 키보드의 작업 키를 누르세요"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"수정됨"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"잠금 해제하여 보기"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"잠금 해제하여 코드 보기"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"컨텍스트 교육"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"터치패드를 사용하여 돌아가기"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"세 손가락을 사용해 왼쪽 또는 오른쪽으로 스와이프하세요. 더 많은 동작을 알아보려면 탭하세요."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"알 수 없음"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"모든 타일을 재설정하시겠습니까?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"모든 빠른 설정 타일이 기기의 원래 설정으로 재설정됩니다."</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index b411bdcaf466..23cc8ee23fb6 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> менен туташкан."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> менен туташты."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Топту жайып көрсөтүү."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Түзмөктү топко кошуңуз."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Түзмөктү топтон алып салыңыз."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Колдонмону ачуу."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Интернет жок."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Киргизүү"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Угуу аппараттары"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Күйгүзүлүүдө…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Жарыктыкты тууралоого болбойт, анткени аны жогорку колдонмо көзөмөлдөйт"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авто буруу"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Экранды авто буруу"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Жайгашкан жер"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Айланадагы үндөр"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Сол"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Оң"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Cол жана оң жактагы өзүнчө башкаруу элементтерине жайып көрсөтүү"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Бирдиктүү башкаруу элементине жыйыштыруу"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Айланадагы үндөрдү басуу"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Айланадагы үндөрдү чыгаруу"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Куралдар"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Ыкчам коштомо жазуулар"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Учкай маалымат"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Түзмөктүн микрофонун бөгөттөн чыгарасызбы?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Түзмөктүн камерасын бөгөттөн чыгарасызбы?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Колдонмону оңго жылдырып, экранды бөлүү"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Колдонмону солго жылдырып, экранды бөлүү"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Толук экранды колдонуу"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Компьютердик версияны колдонуу"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Бөлүнгөн экранда сол же төмөн жактагы колдонмого которулуу"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Бөлүнгөн экранды колдонуп жатканда сол же жогору жактагы колдонмого которулуңуз"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Экранды бөлүү режиминде бир колдонмону экинчисине алмаштыруу"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Анча маанилүү эмес билдирменин сүрөтчөлөрүн көрсөтүү"</string>
<string name="other" msgid="429768510980739978">"Башка"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"плитканын өлчөмүн которуштуруу"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ыкчам баскычты өчүрүү"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"аягына карта кошуу"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Ыкчам баскычты жылдыруу"</string>
@@ -1001,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>-позицияга кошуу"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Абал жараксыз."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>-позиция"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Карточка кошулган"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Карта кошулду"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Карта өчүрүлдү"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ыкчам параметрлер түзөткүчү."</string>
@@ -1545,10 +1551,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Акыркы колдонмолорду көрүү үчүн сенсордук тактаны үч манжаңыз менен өйдө сүрүп, кармап туруңуз"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Бардык колдонмолоруңузду көрүү үчүн баскычтобуңуздагы аракет баскычын басыңыз"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Жашырылды"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Көрүү үчүн кулпусун ачыңыз"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Кодду көрүү үчүн кулпусун ачыңыз"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контексттик билим берүү"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Артка кайтуу үчүн сенсордук тактаны колдонуңуз"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Үч манжаңыз менен солго же оңго сүрүңүз. Башка жаңсоолорду үйрөнүү үчүн таптаңыз."</string>
@@ -1571,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Белгисиз"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Бардык параметрлерди кайра коесузбу?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Бардык ыкчам параметрлер түзмөктүн баштапкы маанилерине кайтарылат"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index e9e585eda056..69b79b89cb80 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ເຊື່ອມ​ຕໍ່​ຫາ <xliff:g id="BLUETOOTH">%s</xliff:g> ແລ້ວ."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="CAST">%s</xliff:g> ແລ້ວ."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ຂະຫຍາຍກຸ່ມ."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ເພີ່ມອຸປະກອນໃສ່ໃນກຸ່ມ."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ລຶບອຸປະກອນອອກຈາກກຸ່ມ."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ເປີດແອັບພລິເຄຊັນ."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ບໍ່ໄດ້ເຊື່ອມຕໍ່."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ໂຣມມິງ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ການປ້ອນຂໍ້ມູນ"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ເຄື່ອງຊ່ວຍຟັງ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ກຳລັງເປີດ..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ບໍ່ສາມາດປັບຄວາມສະຫວ່າງໄດ້ເນື່ອງຈາກຄວບຄຸມໂດຍແອັບທາງເທິງ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ໝຸນ​ອັດ​ຕະ​ໂນ​ມັດ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ສະຖານທີ່"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ສຽງແວດລ້ອມ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ຊ້າຍ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ຂວາ"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"ສຽງແວດລ້ອມ"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"ສຽງແວດລ້ອມຂ້າງຊ້າຍ"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"ສຽງແວດລ້ອມຂ້າງຂວາ"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ຂະຫຍາຍເປັນການຄວບຄຸມທີ່ແຍກເບື້ອງຊ້າຍ ແລະ ຂວາ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ຫຍໍ້ລົງເປັນການຄວບຄຸມແບບຮວມ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ປິດສຽງແວດລ້ອມ"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"ເຊົາປິດສຽງແວດລ້ອມ"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ເຄື່ອງມື"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"ຄຳບັນຍາຍສົດ"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ບັນທຶກ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ປົດບລັອກໄມໂຄຣໂຟນອຸປະກອນບໍ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ປົດບລັອກກ້ອງຖ່າຍຮູບອຸ​ປະ​ກອນບໍ?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ໃຊ້ໂໝດແບ່ງໜ້າຈໍໂດຍໃຫ້ແອັບຢູ່ເບື້ອງຂວາ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ໃຊ້ໂໝດແບ່ງໜ້າຈໍໂດຍໃຫ້ແອັບຢູ່ເບື້ອງຊ້າຍ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ໃຊ້ແບບເຕັມຈໍ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ໃຊ້ມຸມມອງເດັສທັອບ"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"ສະຫຼັບໄປໃຊ້ແອັບຢູ່ຂວາ ຫຼື ທາງລຸ່ມໃນຂະນະທີ່ໃຊ້ແບ່ງໜ້າຈໍ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ສະຫຼັບໄປໃຊ້ແອັບຢູ່ຊ້າຍ ຫຼື ທາງເທິງໃນຂະນະທີ່ໃຊ້ແບ່ງໜ້າຈໍ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ໃນລະຫວ່າງແບ່ງໜ້າຈໍ: ໃຫ້ປ່ຽນຈາກແອັບໜຶ່ງເປັນອີກແອັບໜຶ່ງ"</string>
@@ -1000,6 +1003,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ເພີ່ມໃສ່ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ຕຳແໜ່ງບໍ່ຖືກຕ້ອງ."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ເພີ່ມແຜ່ນແລ້ວ"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ລຶບແຜ່ນແລ້ວ"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ຕົວແກ້ໄຂການຕັ້ງຄ່າດ່ວນ"</string>
@@ -1568,4 +1573,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ບໍ່ຮູ້ຈັກ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ຣີເຊັດແຜ່ນທັງໝົດບໍ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ແຜ່ນການຕັ້ງຄ່າດ່ວນທັງໝົດຈະຣີເຊັດເປັນການຕັ້ງຄ່າແບບເກົ່າຂອງອຸປະກອນ"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 0d4398eb97bc..1ed102d9869b 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Prisijungta prie „<xliff:g id="BLUETOOTH">%s</xliff:g>“."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Prisijungta prie <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Išskleisti grupę."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Pridėti įrenginį prie grupės."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Pašalinti įrenginį iš grupės."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Atidaryti programą."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Neprijungta."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Tarptinklinis ryšys"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Įvestis"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Klausos aparatai"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Įjungiama…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Negalima koreguoti šviesumo, nes jį valdo viršuje esanti programa"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatinis pasukimas"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatiškai sukti ekraną"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Vietovė"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Aplinka"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kairė"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dešinė"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Aplinka"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Aplinka kairėje"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Aplinka dešinėje"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Išskleisti į atskirus kairįjį ir dešinįjį valdiklius"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sutraukti į bendrą valdiklį"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Nutildyti aplinką"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Įjungti aplinkos garsą"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Įrankiai"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtitrai realiuoju laiku"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"Nustatymai"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Pastaba"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Panaikinti įrenginio mikrofono blokavimą?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Panaikinti įrenginio fotoaparato blokavimą?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Naudokite išskaidyto ekrano režimą su programa dešinėje"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Naudokite išskaidyto ekrano režimą su programa kairėje"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Naudoti viso ekrano režimą"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Naudoti rodinio versiją staliniams kompiuteriams"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Perjunkite į programą dešinėje arba apačioje išskaidyto ekrano režimu"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Perjunkite į programą kairėje arba viršuje išskaidyto ekrano režimu"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Išskaidyto ekrano režimu: pakeisti iš vienos programos į kitą"</string>
@@ -1000,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridėkite <xliff:g id="POSITION">%1$d</xliff:g> pozicijoje"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Padėtis netinkama."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> pozicija"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Išklotinė jau pridėta"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Išklotinė pridėta"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Išklotinė pašalinta"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Sparčiųjų nustatymų redagavimo priemonė."</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nežinoma"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Iš naujo nustatyti visus išklotines elementus?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Visi sparčiųjų nustatymų išklotinės elementai bus iš naujo nustatyti į pradinius įrenginio nustatymus"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 60e1e6f67c42..06bc60102c48 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ir izveidots savienojum ar <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Savienots ar ierīci <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Izvērst grupu."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Pievienot ierīci grupai."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Noņemt ierīci no grupas."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Atvērt lietojumprogrammu."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Savienojums nav izveidots."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Viesabonēšana"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ievade"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Dzirdes aparāti"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Notiek ieslēgšana…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nevar mainīt spilgtumu, jo to kontrolē lietotne augšpusē"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automātiska pagriešana"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automātiska ekrāna pagriešana"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Atrašanās vieta"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Apkārtnes skaņas"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Pa kreisi"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Pa labi"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Izvērst, lai rādītu atsevišķu kreiso un labo vadīklu"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sakļaut, lai rādītu vienotu vadīklu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Izslēgt apkārtnes skaņas"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ieslēgt apkārtnes skaņas"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Rīki"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtitri reāllaikā"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Piezīme"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vai atbloķēt ierīces mikrofonu?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vai vēlaties atbloķēt ierīces kameru?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Izmantot ekrāna sadalīšanu ar lietotni labajā pusē"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Izmantot ekrāna sadalīšanu ar lietotni kreisajā pusē"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Izmantot pilnekrāna režīmu"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Izmantot skatu datorā"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Pāriet uz lietotni pa labi/lejā, kamēr izmantojat sadalīto ekrānu."</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pāriet uz lietotni pa kreisi/augšā, kamēr izmantojat sadalīto ekrānu."</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ekrāna sadalīšanas režīmā: pārvietot lietotni no viena ekrāna uz otru"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pievienot elementu pozīcijā numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nederīga pozīcija."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozīcija numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Elements ir pievienots"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Elements ir noņemts"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ātro iestatījumu redaktors."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nezināma"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vai atiestatīt visus elementus?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Visiem ātro iestatījumu elementiem tiks atiestatīti sākotnējie iestatījumi"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 761eac1acae9..c1a5d365c4b0 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Поврзано со <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Поврзано со <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Проширете ја групата."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Додај го уредот во групата."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Отстрани го уредот од групата."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Отворете ја апликацијата."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Не е поврзана"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роаминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Влез"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни помагала"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Се вклучува…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не може да се приспособи осветленоста бидејќи е контролирана од горната апликација"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматско ротирање"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматско ротирање на екранот"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Локација"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Опкружување"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Лево"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Десно"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Прошири на одвоените контроли одлево и оддесно"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Собери на унифицирана контрола"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Исклучи го звукот на опкружувањето"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Вклучи го звукот на опкружувањето"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Алатки"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Автоматски титлови"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Белешка"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Да се одблокира пристапот до микрофонот на уредот?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Да се одблокира пристапот до камерата на уредот?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Користете поделен екран со апликацијата оддесно"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Користете поделен екран со апликацијата одлево"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Користете цел екран"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Користете приказ на компјутер"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Префрлете се на апликацијата десно или долу при користењето поделен екран"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Префрлете се на апликацијата лево или горе при користењето поделен екран"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"При поделен екран: префрлете ги аплик. од едната на другата страна"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Прикажувај икони за известувања со низок приоритет"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"вклучување/исклучување на големината на плочката"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"отстранување на плочката"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"додајте плочка на последната позиција"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Преместување на плочката"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додавање на позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позицијата е погрешна."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Додадена е плочка"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Отстранета е плочка"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Уредник за брзи поставки."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"За да ги видите скорешните апликации, повлечете нагоре и задржете со три прста на допирната подлога"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Притиснете го копчето за дејство на тастатурата за да ги видите сите апликации"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Редактирано"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Отклучете за да прегледате"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Отклучете за да го прегледате кодот"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контекстуално образование"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Користете ја допирната подлога за да се вратите назад"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Повлечете налево или надесно со три прста. Допрете за да научите повеќе движења."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Непознато"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Да се ресетираат сите плочки?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Сите плочки на „Брзи поставки“ ќе се ресетираат на првичните поставки на уредот"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index aae5ac48f64c..fc004c06360c 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> എന്നതിലേക്ക് കണക്‌റ്റുചെയ്‌തു."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ഗ്രൂപ്പ് വികസിപ്പിക്കുക."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ഉപകരണം ഗ്രൂപ്പിലേക്ക് ചേർക്കുക."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ഉപകരണം ഗ്രൂപ്പിൽ നിന്ന് നീക്കം ചെയ്യുക."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ആപ്പ് തുറക്കുക."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"കണക്റ്റുചെയ്‌തിട്ടില്ല."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"റോമിംഗ്"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ഇൻപുട്ട്"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ശ്രവണ സഹായികൾ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ഓണാക്കുന്നു…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"തെളിച്ചം അഡ്‌ജസ്റ്റ് ചെയ്യാനാകില്ല, അത് നിയന്ത്രിക്കുന്നത് ടോപ്പ് ആപ്പാണ്"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"സ്‌ക്രീൻ സ്വയമേവ തിരിയൽ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"സ്‌ക്രീൻ സ്വയമേവ തിരിക്കുക"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ലൊക്കേഷൻ"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"സറൗണ്ടിംഗ്‌സ്"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ഇടത്"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"വലത്"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"വേർതിരിച്ച ഇടത്, വലത് നിയന്ത്രണങ്ങളിലേക്ക് വികസിപ്പിക്കുക"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ഏകീകൃത നിയന്ത്രണത്തിലേക്ക് ചുരുക്കുക"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"സറൗണ്ടിംഗ്‌സ് മ്യൂട്ട് ചെയ്യുക"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"സറൗണ്ടിംഗ്‌സ് അൺമ്യൂട്ട് ചെയ്യുക"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ടൂളുകൾ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"തത്സമയ ക്യാപ്ഷൻ"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"കുറിപ്പ്"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ഉപകരണ മൈക്രോഫോൺ അൺബ്ലോക്ക് ചെയ്യണോ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ഉപകരണ ക്യാമറ അൺബ്ലോക്ക് ചെയ്യണോ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"വലതുവശത്തുള്ള ആപ്പിനൊപ്പം സ്‌ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കുക"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ഇടതുവശത്തുള്ള ആപ്പിനൊപ്പം സ്‌ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കുക"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"പൂർണ സ്ക്രീൻ ഉപയോഗിക്കുക"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ഡെസ്‌ക്ടോപ്പ് വ്യൂ ഉപയോഗിക്കുക"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"സ്ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കുമ്പോൾ വലതുവശത്തെ/താഴത്തെ ആപ്പിലേക്ക് മാറുക"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"സ്ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കുമ്പോൾ ഇടതുവശത്തെ/മുകളിലെ ആപ്പിലേക്ക് മാറൂ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"സ്‌ക്രീൻ വിഭജന മോഡിൽ: ഒരു ആപ്പിൽ നിന്ന് മറ്റൊന്നിലേക്ക് മാറുക"</string>
@@ -1000,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> എന്ന സ്ഥാനത്തേക്ക് ചേർക്കുക"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"സ്ഥാനം അസാധുവാണ്."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"ടൈൽ ഇതിനകം ചേർത്തിട്ടുണ്ട്"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ടൈൽ ചേർത്തു"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ടൈൽ നീക്കം ചെയ്‌തു"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ദ്രുത ക്രമീകരണ എഡിറ്റർ."</string>
@@ -1568,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"അജ്ഞാതം"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"എല്ലാ ടൈലുകളും റീസെറ്റ് ചെയ്യണോ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"എല്ലാ ദ്രുത ക്രമീകരണ ടൈലുകളും ഉപകരണത്തിന്റെ ഒറിജിനൽ ക്രമീകരണത്തിലേക്ക് റീസെറ്റ് ചെയ്യും"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 59388935394c..7d53c479a4b9 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>-тай холбогдсон."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>-д холбогдсон."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Бүлгийг дэлгэнэ үү."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Бүлэгт төхөөрөмж нэмнэ."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Бүлгээс төхөөрөмж хасна."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Аппликейшныг нээнэ үү."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Холбогдоогүй."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Оролт"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Сонсголын төхөөрөмж"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Асааж байна…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Гэрэлтүүлгийг давуу эрхтэй аппаас хянаж байгаа тул тохируулах боломжгүй"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматаар эргэх"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Дэлгэцийг автоматаар эргүүлэх"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Байршил"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Орчин тойрон"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Зүүн"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Баруун"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Зүүн, баруун талын тусдаа тохиргоо руу дэлгэх"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Нэгдсэн тохиргоо руу хураах"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Орчин тойрны дууг хаах"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Орчин тойрны дууг нээх"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Хэрэгсэл"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Шууд тайлбар"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Тэмдэглэл"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Төхөөрөмжийн микрофоныг блокоос гаргах уу?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Төхөөрөмжийн камерыг блокоос гаргах уу?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Аппыг баруун талд байгаагаар дэлгэцийг хуваахыг ашиглах"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Аппыг зүүн талд байгаагаар дэлгэцийг хуваахыг ашиглах"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Бүтэн дэлгэцийг ашиглах"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Дэлгэц дээр харагдах байдлыг ашиглах"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Дэлгэц хуваахыг ашиглаж байхдаа баруун талд эсвэл доор байх апп руу сэлгэ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Дэлгэц хуваахыг ашиглаж байхдаа зүүн талд эсвэл дээр байх апп руу сэлгэ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Дэлгэц хуваах үеэр: аппыг нэгээс нөгөөгөөр солих"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Бага ач холбогдолтой мэдэгдлийн дүрс тэмдгийг харуулах"</string>
<string name="other" msgid="429768510980739978">"Бусад"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"хавтангийн хэмжээг асаах/унтраах"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"хавтанг хасна уу"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"хавтанг сүүлийн байрлалд нэмэх"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Хавтанг зөөх"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> байрлалд нэмнэ үү"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Байрлал буруу байна."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> байрлал"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Хавтан нэмсэн"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Хавтанг хассан"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Шуурхай тохиргоо засварлагч."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Саяхны аппуудыг харахын тулд мэдрэгч самбар дээр гурван хуруугаараа дээш шудраад, удаан дарна уу"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Бүх аппаа харахын тулд гар дээр тань байх тусгай товчлуурыг дарна уу"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Хассан"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Харахын тулд түгжээг тайлна уу"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Кодыг харахын тулд түгжээг тайлна уу"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Хам сэдэвт боловсрол"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Буцахын тулд мэдрэгч самбараа ашиглах"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Гурван хуруугаараа зүүн эсвэл баруун тийш шударна уу. Илүү олон зангаа сурахын тулд товшино уу."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Тодорхойгүй"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Бүх хавтанг шинэчлэх үү?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Шуурхай тохиргооны бүх хавтан төхөөрөмжийн эх тохиргоо руу шинэчлэгдэнэ"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index c3c716055bb0..9eca378c63b3 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> शी कनेक्‍ट केले."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> शी कनेक्ट केले."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"गटाचा विस्तार करा."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"डिव्हाइस गटामध्ये जोडा."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"डिव्हाइस गटामधून काढून टाका."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"अ‍ॅप उघडा."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"कनेक्ट केले नाही."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"रोमिंग"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवणयंत्रे"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सुरू करत आहे…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ब्राइटनेस टॉप ॲपद्वारे नियंत्रित केला जात असल्यामुळे ॲडजस्ट करू शकत नाही"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ऑटो-रोटेट स्क्रीन"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"स्थान"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"जवळपासचे"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"डावे"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"उजवे"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"डाव्या आणि उजव्या स्वतंत्र नियंत्रणांचा विस्तार करा"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"युनिफाइड नियंत्रणासाठी कोलॅप्स करा"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"जवळपासचे आवाज म्यूट करा"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"जवळपासचे आवाज अनम्यूट करा"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"टूल"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"लाइव्ह कॅप्शन"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"टीप"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"डिव्हाइसचा मायक्रोफोन अनब्लॉक करायचा आहे का?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"डिव्हाइसचा कॅमेरा अनब्लॉक करायचा आहे का?"</string>
@@ -696,7 +701,7 @@
<string name="screen_pinning_start" msgid="7483998671383371313">"ॲप पिन केले"</string>
<string name="screen_pinning_exit" msgid="4553787518387346893">"ॲप अनपिन केले"</string>
<string name="stream_voice_call" msgid="7468348170702375660">"कॉल"</string>
- <string name="stream_system" msgid="7663148785370565134">"सिस्टम"</string>
+ <string name="stream_system" msgid="7663148785370565134">"सिस्टीम"</string>
<string name="stream_ring" msgid="7550670036738697526">"रिंग"</string>
<string name="stream_music" msgid="2188224742361847580">"मीडिया"</string>
<string name="stream_alarm" msgid="16058075093011694">"अलार्म"</string>
@@ -741,9 +746,9 @@
<string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> वर प्ले करत आहे"</string>
<string name="media_output_title_without_playing" msgid="3825663683169305013">"यावर ऑडिओ प्ले होईल"</string>
<string name="media_output_title_ongoing_call" msgid="208426888064112006">"यावर कॉल करत आहे"</string>
- <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम UI ट्युनर"</string>
+ <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टीम UI ट्युनर"</string>
<string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string>
- <string name="demo_mode" msgid="263484519766901593">"सिस्टम UI डेमो मोड"</string>
+ <string name="demo_mode" msgid="263484519766901593">"सिस्टीम UI डेमो मोड"</string>
<string name="enable_demo_mode" msgid="3180345364745966431">"डेमो मोड सुरू करा"</string>
<string name="show_demo_mode" msgid="3677956462273059726">"डेमो मोड दर्शवा"</string>
<string name="status_bar_ethernet" msgid="5690979758988647484">"इथरनेट"</string>
@@ -781,12 +786,12 @@
<string name="accessibility_signal_full" msgid="1519655809806462972">"पूर्ण सिग्नल"</string>
<string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाईल"</string>
<string name="tuner_warning_title" msgid="7721976098452135267">"सर्वांसाठी नाही तर काहींसाठी मजेदार असू शकते"</string>
- <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनर आपल्‍याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्‍याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्‍ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्‍यातील रिलीज मध्‍ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string>
+ <string name="tuner_warning" msgid="1861736288458481650">"सिस्टीम UI ट्युनर आपल्याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्यातील रिलीझ मध्ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string>
<string name="tuner_persistent_warning" msgid="230466285569307806">"ही प्रयोगात्मक वैशिष्‍ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्‍यातील रिलीज मध्‍ये कदाचित दिसणार नाहीत."</string>
<string name="got_it" msgid="477119182261892069">"समजले"</string>
- <string name="tuner_toast" msgid="3812684836514766951">"अभिनंदन! सिस्टम UI ट्युनर सेटिंग्जमध्‍ये जोडले गेले आहे"</string>
+ <string name="tuner_toast" msgid="3812684836514766951">"अभिनंदन! सिस्टीम UI ट्युनर सेटिंग्जमध्ये जोडले गेले आहे"</string>
<string name="remove_from_settings" msgid="633775561782209994">"सेटिंग्ज मधून काढा"</string>
- <string name="remove_from_settings_prompt" msgid="551565437265615426">"सेटिंग्ज मधून सिस्टम UI ट्युनर काढून त्याची सर्व वैशिष्ट्‍ये वापरणे थांबवायचे?"</string>
+ <string name="remove_from_settings_prompt" msgid="551565437265615426">"सेटिंग्ज मधून सिस्टीम UI ट्युनर काढून त्याची सर्व वैशिष्ट्ये वापरणे थांबवायचे?"</string>
<string name="enable_bluetooth_title" msgid="866883307336662596">"ब्लूटूथ सुरू करायचे?"</string>
<string name="enable_bluetooth_message" msgid="6740938333772779717">"तुमचा कीबोर्ड तुमच्या टॅबलेटसह कनेक्ट करण्यासाठी, तुम्ही प्रथम ब्लूटूथ सुरू करणे आवश्यक आहे."</string>
<string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"सुरू करा"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ॲप उजवीकडे ठेवून स्प्लिट स्क्रीन वापरा"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ॲप डावीकडे ठेवून स्प्लिट स्क्रीन वापरा"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"फुल स्क्रीन वापरा"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"डेस्कटॉप दृश्य पहा"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"स्प्लिट स्क्रीन वापरताना उजवीकडील किंवा खालील अ‍ॅपवर स्विच करा"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रीन वापरताना डावीकडील किंवा वरील अ‍ॅपवर स्विच करा"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रीनदरम्यान: एक अ‍ॅप दुसऱ्या अ‍ॅपने बदला"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> स्थानावर जोडा"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"स्थान चुकीचे आहे."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थान <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल जोडली"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल काढून टाकली"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"द्रुत सेटिंग्ज संपादक."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"अज्ञात"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"सर्व टाइल रीसेट करायच्या?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"सर्व क्विक सेटिंग्ज टाइल डिव्हाइसच्या मूळ सेटिंग्जवर रीसेट केल्या जातील"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 3c96d0a502a8..7f7c77743b5d 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Disambungkan kepada <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Disambungkan ke <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Kembangkan kumpulan."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Tambahkan peranti pada kumpulan."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Alih keluar peranti daripada kumpulan."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Buka aplikasi."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Tidak disambungkan."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Perayauan"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu pendengaran"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Menghidupkan…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Tidak dapat melaraskan kecerahan kerana peranti dikawal oleh apl bahagian atas"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoputar"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoputar skrin"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasi"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Persekitaran"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kiri"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kanan"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Persekitaran"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Persekitaran kiri"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Persekitaran kanan"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Kembangkan kepada kawalan berasingan sebelah kiri dan kanan"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Kuncupkan kepada kawalan yang disatukan"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Redamkan persekitaran"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Nyahredam persekitaran"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Alatan"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Sari Kata Langsung"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Nyahsekat mikrofon peranti?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Nyahsekat kamera peranti?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Gunakan skrin pisah dengan apl pada sebelah kanan"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Gunakan skrin pisah dengan apl pada sebelah kiri"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Gunakan skrin penuh"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Gunakan paparan desktop"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Tukar kepada apl di sebelah kanan/bawah semasa menggunakan skrin pisah"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Tukar kepada apl di sebelah kiri/atas semasa menggunakan skrin pisah"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Semasa skrin pisah: gantikan apl daripada satu apl kepada apl lain"</string>
@@ -1000,6 +1003,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan pada kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kedudukan tidak sah."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Jubin ditambah"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Jubin dialih keluar"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor tetapan pantas."</string>
@@ -1568,4 +1573,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tidak diketahui"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Tetapkan semula semua jubin?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Semua jubin Tetapan Pantas akan ditetapkan semula kepada tetapan asal peranti"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index b259e3996220..42cfe5eeb5fb 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>သို့ ချိတ်ဆက်ထား"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> သို့ချိတ်ဆက်ထားပါသည်။"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"အုပ်စုကို ပိုပြသည်။"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"အဖွဲ့သို့ စက်ပစ္စည်းထည့်ရန်။"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"အဖွဲ့မှ စက်ပစ္စည်းကို ဖယ်ရှားရန်။"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"အပလီကေးရှင်းကို ဖွင့်သည်။"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ချိတ်ဆက်မထားပါ"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ပြင်ပကွန်ရက်သုံးခြင်း"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"အဝင်"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"နားကြားကိရိယာ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ဖွင့်နေသည်…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"အပေါ်အက်ပ်မှ ထိန်းချုပ်ထားသောကြောင့် တောက်ပမှုကို ချိန်ညှိ၍ မရပါ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"တည်နေရာ"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ဝန်းကျင်အသံ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ဘယ်"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ညာ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ဘယ်ညာခွဲထားသော ထိန်းချုပ်မှုများအဖြစ် ပိုပြပါ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ပေါင်းစည်းထားသော ထိန်းချုပ်မှုအဖြစ် လျှော့ပြပါ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ဝန်းကျင်အသံ ပိတ်ရန်"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"ဝန်းကျင်အသံ ပြန်ဖွင့်ရန်"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"တူးလ်များ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"တိုက်ရိုက်စာတန်း"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"မှတ်စု"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"စက်၏မိုက်ခရိုဖုန်းကို ပြန်ဖွင့်မလား။"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"စက်၏ကင်မရာကို ပြန်ဖွင့်မလား။"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"အက်ပ်ကို ညာ၌ထားကာ မျက်နှာပြင် ခွဲ၍ပြသခြင်း သုံးရန်"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"အက်ပ်ကို ဘယ်၌ထားကာ မျက်နှာပြင် ခွဲ၍ပြသခြင်း သုံးရန်"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ဖန်သားပြင်အပြည့် သုံးရန်"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ဒက်စ်တော့မြင်ကွင်း သုံးရန်"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"မျက်နှာပြင်ခွဲ၍ပြသခြင်း သုံးစဉ် ညာ (သို့) အောက်ရှိအက်ပ်သို့ ပြောင်းရန်"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသုံးစဉ် ဘယ် (သို့) အထက်ရှိအက်ပ်သို့ ပြောင်းရန်"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"မျက်နှာပြင် ခွဲ၍ပြသစဉ်- အက်ပ်တစ်ခုကို နောက်တစ်ခုနှင့် အစားထိုးရန်"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"အရေးမကြီးသော အကြောင်းကြားချက် သင်္ကေတများ ပြရန်"</string>
<string name="other" msgid="429768510980739978">"အခြား"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"အကွက်ငယ်၏ အရွယ်အစားကို ပြောင်းရန်"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"အကွက်ငယ်ကို ဖယ်ရှားရန်"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"နောက်ဆုံးနေရာတွင် အကွက်ငယ် ထည့်ရန်"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"အကွက်ငယ်ကို ရွှေ့ရန်"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထားသို့ ပေါင်းထည့်ရန်"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"နေရာ မမှန်ပါ။"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထား"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"အကွက်ငယ်ကို ထည့်ပြီးပါပြီ"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"အကွက်ငယ်ကို ဖယ်ရှားပြီးပါပြီ"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"မြန်ဆန်သည့် ဆက်တင်တည်းဖြတ်စနစ်"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"လတ်တလောအက်ပ်များကို ကြည့်ရန် တာ့ချ်ပက်ပေါ်တွင် လက်သုံးချောင်းဖြင့် အပေါ်သို့ပွတ်ဆွဲပြီး ဖိထားပါ"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"သင့်အက်ပ်အားလုံးကြည့်ရန် ကီးဘုတ်ပေါ်ရှိ လုပ်ဆောင်ချက်ကီးကို နှိပ်ပါ"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"အစားထိုးထားသည်"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ကြည့်ရန် ဖွင့်ပါ"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ကုဒ်ကြည့်ရန် ဖွင့်ပါ"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"အကြောင်းအရာအလိုက် ပညာရေး"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"နောက်ပြန်သွားရန် သင့်တာ့ချ်ပက်ကို သုံးပါ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"လက်သုံးချောင်းဖြင့် ဘယ် (သို့) ညာသို့ ပွတ်ဆွဲပါ။ လက်ဟန်များ ပိုမိုလေ့လာရန် တို့ပါ။"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"အမျိုးအမည်မသိ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"အကွက်ငယ်အားလုံးကို ပြင်ဆင်သတ်မှတ်မလား။"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"အမြန်ဆက်တင်များ အကွက်ငယ်အားလုံးကို စက်ပစ္စည်း၏ မူရင်းဆက်တင်များသို့ ပြင်ဆင်သတ်မှတ်ပါမည်"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>၊ <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 1c655f1859ad..3b6e891708bc 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Koblet til <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Koblet til <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Utvid gruppen."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Legg til enheten i gruppen."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Fjern enheten fra gruppen."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Åpne appen."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Ikke tilkoblet."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Innenhet"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Slår på …"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kan ikke justere lysstyrken, fordi den kontrolleres av den øvre appen"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotér automatisk"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotér skjermen automatisk"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Sted"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgivelser"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Venstre"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Høyre"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Utvid til separate kontroller for venstre og høyre"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Skjul til samlet kontroll"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Kutt lyden for omgivelsene"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Slå på lyden for omgivelsene"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Verktøy"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Direkteteksting"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Merknad"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vil du oppheve blokkeringen av enhetsmikrofonen?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vil du oppheve blokkeringen av enhetskameraet?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Bruk delt skjerm med appen til høyre"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Bruk delt skjerm med appen til venstre"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Bruk fullskjerm"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Bruk datamaskinvisning"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Bytt til appen til høyre eller under mens du bruker delt skjerm"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bytt til appen til venstre eller over mens du bruker delt skjerm"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"I delt skjerm: Bytt ut en app"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for varsler med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Annet"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"bytt størrelse på brikken"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjerne infobrikken"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"legge til en brikke på den siste posisjonen"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flytt infobrikken"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Legg til posisjonen <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisjonen er ugyldig."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisjon <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"En infobrikke er lagt til"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"En infobrikke er fjernet"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigeringsvindu for hurtiginnstillinger."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"For å se nylige apper, sveip opp og hold med tre fingre på styreflaten"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"For å se alle appene dine, trykk på handlingstasten på tastaturet"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Fjernet"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Lås opp for å se"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Lås opp for å se koden"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstuell opplæring"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Bruk styreflaten for å gå tilbake"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Sveip til venstre eller høyre med tre fingre. Trykk for å lære flere bevegelser."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ukjent"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vil du tilbakestille alle brikkene?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle brikker for hurtiginnstillinger tilbakestilles til enhetens opprinnelige innstillinger"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 133003eb71c1..936a0a653220 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> मा जडित।"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> मा कनेक्ट गरियो।"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"समूह एक्स्पान्ड गर्नुहोस्।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"समूहमा डिभाइस हाल्नुहोस्।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"समूहबाट डिभाइस हटाउनुहोस्।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"एप खोल्नुहोस्।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"जडान नगरिएको।"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"रोमिङ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"हियरिङ डिभाइसहरू"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सक्रिय गर्दै…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"सिरानको एपले चमक नियन्त्रण गरिरहेकाले चमक मिलाउन मिल्दैन"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"अटो रोटेट"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रिन स्वतःघुम्ने"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"लोकेसन"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"वरपरका आवाज"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"बायाँ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"दायाँ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"दायाँ र बायाँतर्फको भोल्युम छुट्टाछुट्टै व्यवस्थापन गर्न भोल्युम प्यानल छुट्ट्याउनुहोस्"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"कोल्याप्स गरी एउटै कन्ट्रोल बनाउनुहोस्"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"वरपरका आवाज म्युट गर्नुहोस्"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"वरपरका आवाज अनम्युट गर्नुहोस्"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"टुलहरू"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"लाइभ क्याप्सन"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"नोट"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"डिभाइसको माइक्रोफोन अनब्लक गर्ने हो?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"डिभाइसको क्यामेरा अनब्लक गर्ने हो?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"हालको एप दायाँ भागमा पारेर स्प्लिट स्क्रिन प्रयोग गर्नुहोस्"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"हालको एप बायाँ भागमा पारेर स्प्लिट स्क्रिन प्रयोग गर्नुहोस्"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"फुल स्क्रिन प्रयोग गर्नुहोस्"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"डेस्कटप भ्यू प्रयोग गर्नुहोस्"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"स्प्लिट स्क्रिन प्रयोग गर्दै गर्दा दायाँ वा तलको एप चलाउनुहोस्"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रिन प्रयोग गर्दै गर्दा बायाँ वा माथिको एप चलाउनुहोस्"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रिन प्रयोग गरिएका बेला: एउटा स्क्रिनमा भएको एप अर्कोमा लैजानुहोस्"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकताका सूचना आइकनहरू देखाउनुहोस्"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"टाइलको आकार टगल गर्नुहोस्"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाउनुहोस्"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"अन्तिम स्थानमा टाइल हाल्नुहोस्"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल सार्नुहोस्"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल यो अवस्था <xliff:g id="POSITION">%1$d</xliff:g> मा हाल्नुहोस्"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"पोजिसन अवैध छ।"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थिति <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल हालियो"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल हटाइयो"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"द्रुत सेटिङ सम्पादक।"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"आफूले हालसालै चलाएका एपहरू हेर्न तीन वटा औँलाले टचप्याडमा माथितिर स्वाइप गर्नुहोस् र होल्ड गर्नुहोस्"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"आफ्ना सबै एपहरू हेर्न आफ्नो किबोर्डमा भएको एक्सन की थिच्नुहोस्"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"जानकारी लुकाउन सम्पादन गरिएको"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"हेर्न अनलक गर्नुहोस्"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"कोड हेर्न अनलक गर्नुहोस्"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"सान्दर्भिक शिक्षा"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"पछाडि जान आफ्नो टचप्याड प्रयोग गर्नुहोस्"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"तीन वटा औँला प्रयोग गरी बायाँ वा दायाँतिर स्वाइप गर्नुहोस्। थप जेस्चर प्रयोग गर्ने तरिका सिक्न ट्याप गर्नुहोस्।"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"अज्ञात"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"सबै टाइलहरू रिसेट गर्ने हो?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"द्रुत सेटिङका सबै टाइलहरू रिसेट गरी डिभाइसका मूल सेटिङ लागू गरिने छन्"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 388190702462..2a81bb424c8e 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Verbonden met <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Verbonden met <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Groep uitvouwen."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Voeg het apparaat aan de groep toe."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Verwijder het apparaat uit de groep."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"App openen."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Niet verbonden."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hoortoestellen"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aanzetten…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kan de helderheid niet aanpassen omdat deze wordt beheerd door de bovenste app"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatisch draaien"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Scherm automatisch draaien"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Locatie"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgevingsgeluid"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Links"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Rechts"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Omgevingsgeluid"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Omgevingsgeluid links"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Omgevingsgeluid rechts"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Uitvouwen naar gescheiden bediening voor links en rechts"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Samenvouwen tot geïntegreerde bediening"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Omgevingsgeluid uitzetten"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Omgevingsgeluid aanzetten"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Tools"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live ondertiteling"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Notitie"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Microfoon van apparaat niet meer blokkeren?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Apparaatcamera niet meer blokkeren?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Gesplitst scherm gebruiken met de app aan de rechterkant"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Gesplitst scherm gebruiken met de app aan de linkerkant"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Volledig scherm gebruiken"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Desktopweergave gebruiken"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Naar de app rechts of onderaan gaan als je een gesplitst scherm gebruikt"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Naar de app links of bovenaan gaan als je een gesplitst scherm gebruikt"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Tijdens gesplitst scherm: een app vervangen door een andere"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Iconen voor meldingen met lage prioriteit tonen"</string>
<string name="other" msgid="429768510980739978">"Overig"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"het formaat van de tegel schakelen"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"tegel verwijderen"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"tegel toevoegen op de laatste positie"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Tegel verplaatsen"</string>
@@ -1001,6 +1003,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Toevoegen aan positie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positie ongeldig."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Positie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tegel toegevoegd"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tegel verwijderd"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor voor \'Snelle instellingen\'."</string>
@@ -1545,10 +1549,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Als je recente apps wilt bekijken, swipe je met 3 vingers omhoog op de touchpad en houd je vast"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Als je alle apps wilt bekijken, druk je op de actietoets op je toetsenbord"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Verborgen"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Ontgrendelen om te bekijken"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Ontgrendelen om de code te bekijken"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextuele educatie"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Je touchpad gebruiken om terug te gaan"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe met 3 vingers naar links of rechts. Tik voor meer gebaren."</string>
@@ -1571,4 +1573,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Onbekend"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Alle tegels resetten?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle tegels voor Snelle instellingen worden teruggezet naar de oorspronkelijke instellingen van het apparaat"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 9c561c92d2ff..e12fe7ecb39e 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ସହ ସଂଯୁକ୍ତ"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ସହିତ ସଂଯୁକ୍ତ।"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ଗ୍ରୁପକୁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ଗ୍ରୁପରେ ଡିଭାଇସ ଯୋଗ କରନ୍ତୁ।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ଗ୍ରୁପରୁ ଡିଭାଇସ କାଢ଼ି ଦିଅନ୍ତୁ।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ଆପ୍ଲିକେସନ ଖୋଲନ୍ତୁ।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"କନେକ୍ଟ ହୋଇନାହିଁ।"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ରୋମିଙ୍ଗ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ଇନପୁଟ୍"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ଅନ୍ ହେଉଛି…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ଟପ ଆପ ଦ୍ୱାରା ଉଜ୍ଜ୍ୱଳତା ନିୟନ୍ତ୍ରିତ ହେଉଥିବା ଯୋଗୁଁ ଏହାକୁ ଆଡଜଷ୍ଟ କରିପାରିବେ ନାହିଁ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ଅଟୋ-ରୋଟେଟ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ଲୋକେସନ"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ପରିପାର୍ଶ୍ୱ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ବାମ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ଡାହାଣ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ବାମ ଏବଂ ଡାହାଣ ଅଲଗା ନିୟନ୍ତ୍ରଣକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ଏକତ୍ରିତ ନିୟନ୍ତ୍ରଣକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ପରିପାର୍ଶ୍ୱକୁ ମ୍ୟୁଟ କରନ୍ତୁ"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"ପରିପାର୍ଶ୍ୱକୁ ଅନମ୍ୟୁଟ କରନ୍ତୁ"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ଟୁଲ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"ଲାଇଭ କେପ୍ସନ"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ନୋଟ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଅନବ୍ଲକ କରିବେ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ଡିଭାଇସର କେମେରାକୁ ଅନବ୍ଲକ କରିବେ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ଡାହାଣରେ ଆପ ସହିତ ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ବାମରେ ଆପ ସହିତ ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ଡେସ୍କଟପ ଭ୍ୟୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବା ସମୟରେ ଡାହାଣପଟର ବା ତଳର ଆପକୁ ସୁଇଚ କରନ୍ତୁ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବା ସମୟରେ ବାମପଟର ବା ଉପରର ଆପକୁ ସୁଇଚ କରନ୍ତୁ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ସମୟରେ: କୌଣସି ଆପକୁ ଗୋଟିଏରୁ ଅନ୍ୟ ଏକ ଆପରେ ବଦଳାନ୍ତୁ"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"କମ୍‍-ଅଗ୍ରାଧିକାର ବିଜ୍ଞପ୍ତି ଆଇକନ୍‍ ଦେଖାନ୍ତୁ"</string>
<string name="other" msgid="429768510980739978">"ଅନ୍ୟ"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ଟାଇଲର ସାଇଜକୁ ଟୋଗଲ କରନ୍ତୁ"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ଟାଇଲ୍ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"ଶେଷ ପୋଜିସନରେ ଟାଇଲ ଯୋଗ କରିବା"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ଟାଇଲ୍ ମୁଭ୍ କରନ୍ତୁ"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ଅବସ୍ଥିତିରେ ଯୋଗ କରନ୍ତୁ"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ଅବସ୍ଥିତି ଅବୈଧ ଅଟେ।"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ଅବସ୍ଥିତି <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ଟାଇଲ୍ ଯୋଗ କରାଯାଇଛି"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ଟାଇଲ୍ କାଢ଼ି ଦିଆଯାଇଛି"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ଦ୍ରୁତ ସେଟିଙ୍ଗ ଏଡିଟର୍।"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"ବର୍ତ୍ତମାନର ଆପ୍ସ ଭ୍ୟୁ କରିବାକୁ, ଟଚପେଡରେ ତିନୋଟି ଆଙ୍ଗୁଠିରେ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"ଆପଣଙ୍କ ସମସ୍ତ ଆପ୍ସ ଭ୍ୟୁ କରିବା ପାଇଁ ଆପଣଙ୍କ କୀବୋର୍ଡରେ ଆକ୍ସନ କୀ\'କୁ ଦବାନ୍ତୁ"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"ଲୁଚା ଯାଇଥିବା"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ଭ୍ୟୁ କରିବାକୁ ଅନଲକ କରନ୍ତୁ"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ଭ୍ୟୁ କରିବାକୁ ଅନଲକ କରିବା କୋଡ"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"ପ୍ରାସଙ୍ଗିକ ଶିକ୍ଷା"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ପଛକୁ ଫେରିବା ପାଇଁ ଆପଣଙ୍କ ଟଚପେଡକୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ତିନୋଟି ଆଙ୍ଗୁଠିରେ ବାମ ବା ଡାହାଣକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଜେଶ୍ଚରଗୁଡ଼ିକ ବିଷୟରେ ଅଧିକ ଜାଣିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ଅଜଣା"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ସମସ୍ତ ଟାଇଲକୁ ରିସେଟ କରିବେ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ସମସ୍ତ କୁଇକ ସେଟିଂସ ଟାଇଲ ଡିଭାଇସର ମୂଳ ସେଟିଂସରେ ରିସେଟ ହୋଇଯିବ"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 60275382cabe..26d26dc1e795 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ।"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ਗਰੁੱਪ ਦਾ ਵਿਸਤਾਰ ਕਰੋ।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ਗਰੁੱਪ ਵਿੱਚ ਡੀਵਾਈਸ ਸ਼ਾਮਲ ਕਰੋ।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ਗਰੁੱਪ ਤੋਂ ਡੀਵਾਈਸ ਹਟਾਓ।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ਐਪਲੀਕੇਸ਼ਨ ਖੋਲ੍ਹੋ।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ।"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ਰੋਮਿੰਗ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ਇਨਪੁੱਟ"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ਚਾਲੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ਚਮਕ ਨੂੰ ਵਿਵਸਥਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਪਹਿਲਾਂ ਤੋਂ ਚੱਲ ਰਹੀ ਐਪ ਇਸਨੂੰ ਕੰਟਰੋਲ ਕਰ ਰਹੀ ਹੈ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ਸਵੈ-ਘੁਮਾਓ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ਸਕ੍ਰੀਨ ਨੂੰ ਆਪਣੇ ਆਪ ਘੁੰਮਾਓ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ਟਿਕਾਣਾ"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ਆਲੇ-ਦੁਆਲੇ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ਖੱਬੇ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ਸੱਜੇ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ਖੱਬੇ ਅਤੇ ਸੱਜੇ ਪਾਸੇ ਦੇ ਸ਼ੋਰ ਨੂੰ ਵੱਖ-ਵੱਖ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ਏਕੀਕ੍ਰਿਤ ਕੰਟਰੋਲ \'ਤੇ ਜਾਣ ਲਈ ਸਮੇਟੋ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ਆਲੇ-ਦੁਆਲੇ ਦੇ ਸ਼ੋਰ ਨੂੰ ਮਿਊਟ ਕਰੋ"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"ਆਲੇ-ਦੁਆਲੇ ਦੇ ਸ਼ੋਰ ਨੂੰ ਅਣਮਿਊਟ ਕਰੋ"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ਟੂਲ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"ਲਾਈਵ ਸੁਰਖੀਆਂ"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"ਨੋਟ-ਕਥਨ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ਕੀ ਡੀਵਾਈਸ ਦੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਅਣਬਲਾਕ ਕਰਨਾ ਹੈ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ਕੀ ਡੀਵਾਈਸ ਦੇ ਕੈਮਰੇ ਨੂੰ ਅਣਬਲਾਕ ਕਰਨਾ ਹੈ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ਸੱਜੇ ਪਾਸੇ ਵਾਲੀ ਐਪ ਨਾਲ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ਖੱਬੇ ਪਾਸੇ ਵਾਲੀ ਐਪ ਨਾਲ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ਡੈਸਕਟਾਪ ਦ੍ਰਿਸ਼ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵੇਲੇ ਸੱਜੇ ਜਾਂ ਹੇਠਾਂ ਮੌਜੂਦ ਐਪ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵੇਲੇ ਖੱਬੇ ਜਾਂ ਉੱਪਰ ਮੌਜੂਦ ਐਪ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੌਰਾਨ: ਇੱਕ ਐਪ ਨਾਲ ਦੂਜੀ ਐਪ ਨੂੰ ਬਦਲੋ"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ਸਥਾਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ਮੌਜੂਦਾ ਥਾਂ ਅਵੈਧ ਹੈ।"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ਸਥਾਨ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ਟਾਇਲ ਨੂੰ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ਟਾਇਲ ਨੂੰ ਹਟਾ ਦਿੱਤਾ ਗਿਆ"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਸੰਪਾਦਕ।"</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ਅਗਿਆਤ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ਕੀ ਸਾਰੀਆਂ ਟਾਇਲਾਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ਸਾਰੀਆਂ ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਟਾਇਲਾਂ ਡੀਵਾਈਸ ਦੀਆਂ ਮੂਲ ਸੈਟਿੰਗਾਂ \'ਤੇ ਰੀਸੈੱਟ ਹੋ ਜਾਣਗੀਆਂ"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 35ff6c941b07..6b526f292507 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Połączono z <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Połączono z urządzeniem <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Rozwiń grupę."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodaj urządzenie do grupy."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Usuń urządzenie z grupy."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otwórz aplikację."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nie połączono."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Wejście"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparaty słuchowe"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Włączam…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nie można dostosować jasności, ponieważ reguluje ją aplikacja na pierwszym planie"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoobracanie"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoobracanie ekranu"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokalizacja"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Otoczenie"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Po lewej"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Po prawej"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Rozwiń, aby oddzielić elementy sterujące po lewej i po prawej stronie"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Zwiń do ujednoliconego sterowania"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Wycisz otoczenie"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Wyłącz wyciszenie otoczenia"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Narzędzia"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Napisy na żywo"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Notatka"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Odblokować mikrofon urządzenia?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Odblokować aparat urządzenia?"</string>
@@ -582,13 +587,13 @@
<string name="media_projection_entry_app_permission_dialog_continue_entire_screen" msgid="1850848182344377579">"Udostępnij ekran"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"<xliff:g id="APP_NAME">%1$s</xliff:g> ma wyłączoną tę opcję"</string>
<string name="media_projection_entry_share_app_selector_title" msgid="1419515119767501822">"Wybierz aplikację do udostępniania"</string>
- <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"Włączyć przesyłanie treści wyświetlanych na ekranie?"</string>
+ <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"Włączyć przesyłanie treści z ekranu?"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"Przesyłanie obrazu z 1 aplikacji"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"Przesyłanie całego ekranu"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Kiedy przesyłasz treści z całego ekranu, widoczny jest cały obraz z wyświetlacza. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Kiedy przesyłasz obraz z aplikacji, widoczne jest wszystko to, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Kiedy przesyłasz obraz z aplikacji, widoczne jest wszystko, co jest w niej wyświetlane lub odtwarzane. Uważaj więc na takie treści jak hasła, dane do płatności, wiadomości, zdjęcia, audio czy filmy."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Prześlij ekran"</string>
- <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Wybieranie aplikacji do przesyłania"</string>
+ <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Wybierz aplikację do przesyłania"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Rozpocząć udostępnianie?"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, dźwięku i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, dźwięku i filmów."</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Podziel ekran z aplikacją widoczną po prawej"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Podziel ekran z aplikacją widoczną po lewej"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Użyj trybu pełnoekranowego"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Użyj wersji na komputery"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Przełącz się na aplikację po prawej lub poniżej na podzielonym ekranie"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Przełącz się na aplikację po lewej lub powyżej na podzielonym ekranie"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Podczas podzielonego ekranu: zastępowanie aplikacji"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokazuj ikony powiadomień o niskim priorytecie"</string>
<string name="other" msgid="429768510980739978">"Inne"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"przełącz rozmiar kafelka"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"usunąć kartę"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"dodaj kafelek do ostatniej pozycji"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Przenieś kartę"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodaj w pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nieprawidłowa pozycja."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozycja <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Dodano kartę"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Usunięto kartę"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Edytor szybkich ustawień."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Aby wyświetlić ostatnie aplikacje, przesuń w górę za pomocą 3 palców na touchpadzie i przytrzymaj."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Aby wyświetlić wszystkie swoje aplikacje, naciśnij klawisz działania na klawiaturze"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Usunięto"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Odblokuj, aby zobaczyć"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Odblokuj, aby zobaczyć kod"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Edukacja kontekstowa"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Przechodzenie wstecz za pomocą touchpada"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Przesuń trzema palcami w prawo lub lewo. Kliknij, aby poznać więcej gestów."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nieznane"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Zresetować wszystkie kafelki?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Wszystkie kafelki Szybkich ustawień zostaną zresetowane do oryginalnych ustawień urządzenia"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index fc7e2afd5a2a..07f87ee1a0cc 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expandir grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adicionar dispositivo ao grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remover dispositivo do grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicativo."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Sem conexão."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Não é possível ajustar o brilho, porque ele está sendo controlado pelo app principal"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string>
@@ -351,7 +348,7 @@
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Redes indisponíveis"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Nenhuma rede Wi-Fi disponível"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Ativando…"</string>
- <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmitir"</string>
+ <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmissão"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Som ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lado esquerdo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Lado direito"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Abrir para controles separados da esquerda e da direita"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Fechar para controle unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar som ambiente"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ativar som ambiente"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Ferramentas"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Legenda instantânea"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Observação"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Desbloquear o microfone do dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Desbloquear a câmera do dispositivo?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Usar a tela dividida com o app à direita"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Usar a tela dividida com o app à esquerda"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Usar tela cheia"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Usar a versão para computadores"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Mudar para o app à direita ou abaixo ao usar a tela dividida"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mudar para o app à esquerda ou acima ao usar a tela dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Com a tela dividida: substituir um app por outro"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"alternar o tamanho do bloco"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o bloco"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"adicionar o bloco à última posição"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover bloco"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configurações rápidas."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Se quiser ver os apps recentes, deslize para cima e pressione o touchpad com três dedos"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Para ver todos os apps, pressione a tecla de ação no teclado"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Encoberto"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desbloqueie para ver"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desbloqueie para ver o código"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educação contextual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use o touchpad para voltar"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Deslize para a esquerda ou direita usando três dedos. Toque para aprender outros gestos."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecidos"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Redefinir todos os blocos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os blocos \"Configurações rápidas\" serão redefinidos para as configurações originais do dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 14b85b7e9e3e..de1057d543c8 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ligado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Ligado a <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expanda o grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adicionar dispositivo ao grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remover dispositivo do grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Abra a aplicação."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Sem ligação."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"A ativar..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Não é possível ajustar o brilho porque está a ser controlado pela app principal"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotação auto."</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rodar o ecrã automaticamente"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Esquerda"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Direita"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Ambiente"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Ambiente à esquerda"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Ambiente à direita"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expandir para controlos separados do lado direito e esquerdo"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Reduzir para controlo unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Desativar som do ambiente"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Reativar som do ambiente"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Ferramentas"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Legendas instantâneas"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"Definições"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Nota"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Desbloquear o microfone do dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Desbloquear a câmara do dispositivo?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Use o ecrã dividido com a app à direita"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Use o ecrã dividido com a app à esquerda"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Use o ecrã inteiro"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Use a vista de computador"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Mudar para a app à direita ou abaixo enquanto usa o ecrã dividido"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mude para a app à esquerda ou acima enquanto usa o ecrã dividido"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Durante o ecrã dividido: substituir uma app por outra"</string>
@@ -1000,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicione à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Mosaico já adicionado"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cartão adicionado"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cartão removido"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de definições rápidas."</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecido"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Repor todos os mosaicos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os mosaicos de Definições rápidas vão ser repostos para as definições originais do dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index fc7e2afd5a2a..07f87ee1a0cc 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Expandir grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adicionar dispositivo ao grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remover dispositivo do grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicativo."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Sem conexão."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Não é possível ajustar o brilho, porque ele está sendo controlado pelo app principal"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string>
@@ -351,7 +348,7 @@
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Redes indisponíveis"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Nenhuma rede Wi-Fi disponível"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Ativando…"</string>
- <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmitir"</string>
+ <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmissão"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Som ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lado esquerdo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Lado direito"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Abrir para controles separados da esquerda e da direita"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Fechar para controle unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar som ambiente"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Ativar som ambiente"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Ferramentas"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Legenda instantânea"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Observação"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Desbloquear o microfone do dispositivo?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Desbloquear a câmera do dispositivo?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Usar a tela dividida com o app à direita"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Usar a tela dividida com o app à esquerda"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Usar tela cheia"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Usar a versão para computadores"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Mudar para o app à direita ou abaixo ao usar a tela dividida"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mudar para o app à esquerda ou acima ao usar a tela dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Com a tela dividida: substituir um app por outro"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"alternar o tamanho do bloco"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o bloco"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"adicionar o bloco à última posição"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover bloco"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configurações rápidas."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Se quiser ver os apps recentes, deslize para cima e pressione o touchpad com três dedos"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Para ver todos os apps, pressione a tecla de ação no teclado"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Encoberto"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desbloqueie para ver"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desbloqueie para ver o código"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educação contextual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use o touchpad para voltar"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Deslize para a esquerda ou direita usando três dedos. Toque para aprender outros gestos."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecidos"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Redefinir todos os blocos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os blocos \"Configurações rápidas\" serão redefinidos para as configurações originais do dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index b577be53ac08..a6b4983aa0ae 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectat la <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"S-a stabilit conexiunea la <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Extinde grupul."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adaugă dispozitivul în grup."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Elimină dispozitivul din grup."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Deschide aplicația."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Neconectat."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Intrare"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparate auditive"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Se activează..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nu se poate ajusta luminozitatea deoarece este controlată de aplicația de sus"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotire automată"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotirea automată a ecranului"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Locație"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Împrejurimi"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Stânga"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dreapta"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Extinde comenzile separate la stânga și la dreapta"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Restrânge la comanda unificată"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Dezactivează sunetul ambiental"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Activează sunetul ambiental"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Instrumente"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Subtitrări live"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Notă"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblochezi microfonul dispozitivului?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblochezi camera dispozitivului?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Folosește ecranul împărțit cu aplicația în dreapta"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Folosește ecranul împărțit cu aplicația în stânga"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Folosește ecranul complet"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Folosește afișarea pe desktop"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Treci la aplicația din dreapta sau de mai jos cu ecranul împărțit"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Treci la aplicația din stânga sau de mai sus cu ecranul împărțit"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"În modul ecran împărțit: înlocuiește o aplicație cu alta"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afișează pictogramele de notificare cu prioritate redusă"</string>
<string name="other" msgid="429768510980739978">"Altele"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"comută dimensiunea cardului"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"elimină cardul"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"adaugă cardul în ultima poziție"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mută cardul"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adaugă pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Poziție nevalidă."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cardul a fost adăugat"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cardul a fost eliminat"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editorul pentru setări rapide."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Ca să vezi aplicațiile recente, glisează în sus și ține apăsat cu trei degete pe touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Ca să vezi toate aplicațiile, apasă tasta de acțiuni de pe tastatură"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Ascunsă"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Deblochează pentru a afișa"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Deblochează pentru a vedea codul"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educație contextuală"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Folosește-ți touchpadul ca să revii"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Glisează la stânga sau la dreapta cu trei degete. Atinge ca să înveți mai multe gesturi."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Necunoscută"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Resetezi toate cardurile?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Toate cardurile Setări rapide se vor reseta la setările inițiale ale dispozitivului"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 03b5423d59b6..693024bcb274 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>: подключено."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Подключено к: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Развернуть группу."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Добавить устройство в группу."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Удалить устройство из группы."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Открыть приложение."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Не подключено"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Устройство ввода"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слуховые аппараты"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включение…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Невозможно изменить яркость, поскольку ею управляет приложение сверху"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоповорот"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоповорот экрана"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Геолокация"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Окружающие звуки"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Левый"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Правый"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Окружающие звуки"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Звуки слева"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Звуки справа"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Разделить на левый и правый элемент управления"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Объединить в один элемент управления"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Заглушить окружающие звуки"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Не заглушать окружающие звуки"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Инструменты"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Автоматические субтитры"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Заметка"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Разблокировать микрофон устройства?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Разблокировать камеру устройства?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Разделить экран и поместить открытое приложение справа"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Разделить экран и поместить открытое приложение слева"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Полноэкранный режим"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Версия для ПК"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Перейти к приложению справа или внизу на разделенном экране"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Перейти к приложению слева или вверху на разделенном экране"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"В режиме разделения экрана заменить одно приложение другим"</string>
@@ -1000,6 +1003,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Недопустимое расположение."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Панель добавлена"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Панель удалена"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор быстрых настроек."</string>
@@ -1568,4 +1573,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Неизвестно"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Сбросить все параметры?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Для всех параметров быстрых настроек будут восстановлены значения по умолчанию."</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 15441b57e7d2..8632e13397d2 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> වෙත සම්බන්ධ කරන ලදි."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> වෙත සම්බන්ධ විය."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"සමූහය දිගහැරීම"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"සමූහයට උපාංගය එක් කරන්න."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"උපාංගය සමූහයෙන් ඉවත් කරන්න."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"යෙදුම විවෘත කරන්න."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"සම්බන්ධ වී නැත."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"රෝමිං"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ආදානය"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ශ්‍රවණාධාරක"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ක්‍රියාත්මක කරමින්…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ඉහළ යෙදුම මඟින් එය පාලනය වන නිසා දීප්තිය ගැළපුම් කළ නොහැක"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ස්වයංක්‍රීය කරකැවීම"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ස්වයංක්‍රීයව-භ්‍රමණය වන තිරය"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ස්ථානය"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"වටපිටාව"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"වම"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"දකුණ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"වමට සහ දකුණට වෙන් වූ පාලන වෙත පුළුල් කරන්න"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ඒකාබද්ධ පාලනයට හකුළන්න"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"අවට පරිසරය නිහඬ කරන්න"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"අවට නිහඬ නොකරන්න"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"මෙවලම්"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"සජීවී සිරස්තල"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"සටහන"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"උපාංග මයික්‍රෆෝනය අවහිර කිරීම ඉවත් කරන්නද?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"උපාංග කැමරාව අවහිර කිරීම ඉවත් කරන්නද?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"දකුණේ යෙදුම සමග බෙදීම් තිරය භාවිතා කරන්න"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"වම් පැත්තේ යෙදුම සමග බෙදීම් තිරය භාවිතා කරන්න"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"පූර්ණ තිරය භාවිතා කරන්න"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ඩෙස්ක්ටොප් දසුන භාවිතා කරන්න"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"බෙදුම් තිරය භාවිත කරන අතරතුර දකුණේ හෝ පහළින් ඇති යෙදුමට මාරු වන්න"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"බෙදුම් තිරය භාවිත කරන අතරතුර වමේ හෝ ඉහළ ඇති යෙදුමට මාරු වන්න"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"බෙදුම් තිරය අතරතුර: යෙදුමක් එකකින් තවත් එකක් ප්‍රතිස්ථාපනය කරන්න"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"අඩු ප්‍රමුඛතා දැනුම්දීම් අයිකන පෙන්වන්න"</string>
<string name="other" msgid="429768510980739978">"වෙනත්"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ටයිල් එකේ ප්‍රමාණය මාරු කරන්න"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ටයිල් ඉවත් කරන්න"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"ටයිල් එක අවසාන ස්ථානයට එක් කරන්න"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ටයිල් ගෙන යන්න"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ස්ථානයට එක් කරන්න"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ස්ථානය අවලංගුයි."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ස්ථානය <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ටයිල් එක එක් කරන ලදි"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ටයිල් ඉවත් කරන ලදි"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ඉක්මන් සැකසුම් සංස්කාරකය."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"මෑත යෙදුම් බැලීමට, ඉහළට ස්වයිප් කර ස්පර්ශ පුවරුව මත ඇඟිලි තුනකින් අල්ලාගෙන සිටින්න"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"ඔබේ සියලුම යෙදුම් බැලීමට, ඔබේ යතුරුපුවරුවේ ක්‍රියාකාරී යතුර ඔබන්න"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"නැවත සකස් කරන ලද"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"බැලීමට අගුළු හරින්න"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"කේතය බැලීමට අගුළු හරින්න"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"සන්දර්භීය අධ්‍යාපනය"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ආපසු යාමට ඔබේ ස්පර්ශ පුවරුව භාවිත කරන්න"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ඇඟිලි තුනක් භාවිතයෙන් වමට හෝ දකුණට ස්වයිප් කරන්න. තව ඉංගිත දැන ගැනීමට තට්ටු කරන්න."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"නොදනී"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"සියලු ටයිල් නැවත සකසන්න ද?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"සියලු ඉක්මන් සැකසීම් ටයිල් උපාංගයේ මුල් සැකසීම් වෙත නැවත සකසනු ඇත"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 868e9390e5ef..03509f314b2b 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Pripojené k zariadeniu <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Pripojené k zariadeniu <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Rozbaliť skupinu"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Pridať zariadenie do skupiny"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Odstrániť zariadenie zo skupiny"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otvoriť aplikáciu"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nepripojené."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Načúvadlá"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapína sa…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Jas sa nedá upraviť, pretože ho ovláda horná aplikácia"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčanie"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčanie obrazovky"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Poloha"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okolie"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vľavo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Vpravo"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Okolie"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Okolie zľava"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Okolie sprava"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Rozbaliť na samostatné ovládanie ľavej a pravej strany"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Zbaliť na jednotné ovládanie"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Vypnúť zvuk okolia"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Zapnúť zvuk okolia"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Nástroje"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Živý prepis"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Poznámka"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Chcete odblokovať mikrofón zariadenia?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Chcete odblokovať kameru zariadenia?"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Rozdelenie obrazovky, aktuálna aplikácia vpravo"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Rozdelenie obrazovky, aktuálna aplikácia vľavo"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Používať celú obrazovku"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Používať zobrazenie v počítači"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Prechod na aplikáciu vpravo alebo dole pri rozdelenej obrazovke"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Prechod na aplikáciu vľavo alebo hore pri rozdelenej obrazovke"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Počas rozdelenej obrazovky: nahradenie aplikácie inou"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Zobraziť ikony upozornení s nízkou prioritou"</string>
<string name="other" msgid="429768510980739978">"Ďalšie"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"prepnúť veľkosť karty"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstrániť kartu"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"pridáte kartu na poslednú pozíciu"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Presunúť kartu"</string>
@@ -1001,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridať na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozícia je neplatná."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozícia"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Karta už bola pridaná"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Karta bola pridaná"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Karta bola odstránená"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rýchlych nastavení"</string>
@@ -1545,10 +1548,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Ak si chcete zobraziť nedávne aplikácie, potiahnite po touchpade troma prstami nahor a pridržte ich."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Ak si chcete zobraziť všetky aplikácie, stlačte na klávesnici akčný kláves"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Zamaskované"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Zobrazíte odomknutím"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kód sa zobrazí po odomknutí"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextová náuka"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Prechádzajte späť pomocou touchpadu"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Potiahnite troma prstami doľava alebo doprava. Viac o gestách sa dozviete klepnutím."</string>
@@ -1571,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznáme"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Chcete resetovať všetky karty?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Všetky karty rýchlych nastavení sa resetujú na pôvodné nastavenia zariadenia"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index e6d133ef61f5..10a236c0ed5b 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezava vzpostavljena z: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Vzpostavljena povezava: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Razširitev skupine."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodajte napravo v skupino."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Odstranite napravo iz skupine."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Odpiranje aplikacije."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Ni povezan."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Gostovanje"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vhodna naprava"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Vklapljanje …"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Svetlosti ni mogoče prilagoditi, ker jo nadzoruje aplikacija na vrhu"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Samodejno sukanje"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Samodejno sukanje zaslona"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okolica"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Levo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Okolica"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Okolica na levi"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Okolica na desni"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Razširitev na ločene kontrolnike za levo in desno stran"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Strnitev v enotni kontrolnik"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Izklop okoliškega zvoka"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Vklop okoliškega zvoka"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Orodja"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Samodejni podnapisi"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"Nastavitve"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Opomba"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite odblokirati mikrofon v napravi?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Želite odblokirati fotoaparat v napravi?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Uporaba razdeljenega zaslona z aplikacijo na desni"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Uporaba razdeljenega zaslona z aplikacijo na levi"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Uporaba celozaslonskega načina"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Uporaba pogleda za namizni računalnik"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Preklop na aplikacijo desno ali spodaj med uporabo razdeljenega zaslona"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Preklop na aplikacijo levo ali zgoraj med uporabo razdeljenega zaslona"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Pri razdeljenem zaslonu: medsebojna zamenjava aplikacij"</string>
@@ -991,8 +993,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokaži ikone obvestil z nizko stopnjo prednosti"</string>
<string name="other" msgid="429768510980739978">"Drugo"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"preklop velikosti ploščice"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranitev ploščice"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"dodajanje ploščice na zadnji položaj"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Premik ploščice"</string>
@@ -1001,6 +1002,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj je neveljaven."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Ploščica je že dodana"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ploščica je bila dodana"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ploščica je bila odstranjena"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Urejevalnik hitrih nastavitev."</string>
@@ -1545,10 +1547,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Za ogled nedavnih aplikacij povlecite s tremi prsti navzgor po sledilni ploščici in pridržite"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Za ogled vseh aplikacij pritisnite tipko za dejanja na tipkovnici"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Zakrito"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Odklenite za ogled"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Odklenite za ogled kode"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstualno izobraževanje"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Uporaba sledilne ploščice za pomik nazaj"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"S tremi prsti povlecite levo ali desno. Dotaknite se, če želite spoznati več potez."</string>
@@ -1571,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznano"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite ponastaviti vse ploščice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Vse ploščice v hitrih nastavitvah bodo ponastavljene na prvotne nastavitve naprave."</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 724c6c0d9a41..a32fc8aaf267 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Lidhur me <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Është lidhur me <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Zgjero grupin."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Shto pajisjen te grupi."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Hiq pajisjen nga grupi."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Hap aplikacionin."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nuk është i lidhur."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Hyrja"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparatet e dëgjimit"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Po aktivizohet…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ndriçimi nuk mund të rregullohet pasi po kontrollohet nga aplikacioni lart"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rrotullim automatik"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rrotullimi automatik i ekranit"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Vendndodhja"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ambienti rrethues"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Majtas"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Djathtas"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Zgjero te kontrollet e veçuara majtas dhe djathtas"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Palos te kontrolli i unifikuar"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Vendos në heshtje ambientin rrethues"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Anulo vendosjen në heshtje të ambientit rrethues"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Veglat"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Titrat në çast"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Shënim"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Të zhbllokohet mikrofoni i pajisjes?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Të zhbllokohet kamera e pajisjes?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Përdor ekranin e ndarë me aplikacionin në të djathtë"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Përdor ekranin e ndarë me aplikacionin në të majtë"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Përdor ekranin e plotë"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Përdor pamjen e desktopit"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Kalo tek aplikacioni djathtas ose poshtë kur përdor ekranin e ndarë"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Kalo tek aplikacioni në të majtë ose sipër kur përdor ekranin e ndarë"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Gjatë ekranit të ndarë: zëvendëso një aplikacion me një tjetër"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Shfaq ikonat e njoftimeve me përparësi të ulët"</string>
<string name="other" msgid="429768510980739978">"Të tjera"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ndrysho madhësinë e pllakëzës"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"hiq pllakëzën"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"shtuar pllakëzën në pozicionin e fundit"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Zhvendos pllakëzën"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Shto te pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicion i pavlefshëm."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pllakëza u shtua"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pllakëza u hoq"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redaktori i cilësimeve të shpejta."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Për aplikacionet e fundit, rrëshqit shpejt lart dhe mbaj shtypur me tre gishta në bllokun me prekje"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Për të shikuar të gjitha aplikacionet, shtyp tastin e veprimit në tastierë"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redaktuar"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Shkyçe për ta parë"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Shkyçe për të parë kodin"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Edukimi kontekstual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Përdor bllokun me prekje për t\'u kthyer prapa"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Rrëshqit shpejt majtas ose djathtas duke përdorur tre gishta. Trokit për të mësuar më shumë gjeste."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nuk njihet"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Të rivendosen të gjitha pllakëzat?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Të gjitha pllakëzat e \"Cilësimeve të shpejta\" do të rivendosen te cilësimet origjinale të pajisjes"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index e6471eba854d..f99301c44e7d 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Повезани сте са <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Повезани смо са уређајем <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Проширите групу."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Додајте уређај у групу."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Уклоните уређај из групе."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Отворите апликацију."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Није повезано."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Унос"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни апарати"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Укључује се..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не можете да прилагодите осветљеност јер је контролише апликација у првом плану"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аутоматска ротација"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аутоматско ротирање екрана"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Локација"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Окружење"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Лево"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Десно"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Прошири на контроле раздвојене на леву и десну страну"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Скупи у јединствену контролу"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Искључи звук окружења"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Укључи звук окружења"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Алатке"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Титл уживо"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Белешка"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Желите да одблокирате микрофон уређаја?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Желите да одблокирате камеру уређаја?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Користи подељени екран са апликацијом с десне стране"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Користи подељени екран са апликацијом с леве стране"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Користи приказ преко целог екрана"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Користи приказ за рачунаре"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Пређи у апликацију здесна или испод док је подељен екран"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Пређите у апликацију слева или изнад док користите подељени екран"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"У режиму подељеног екрана: замена једне апликације другом"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додајте на <xliff:g id="POSITION">%1$d</xliff:g>. позицију"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позиција је неважећа."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. позиција"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плочица је додата"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плочица је уклоњена"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Уређивач за Брза подешавања."</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Непознато"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Желите да ресетујете све плочице?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Све плочице Брзих подешавања ће се ресетовати на првобитна подешавања уређаја"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 9f2a2083175b..0085ed26e4cd 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ansluten till <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Ansluten till <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Utöka gruppen."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Lägg till enheten i gruppen."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Ta bort enheten från gruppen."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Öppna appen."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Inte ansluten."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingång"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörapparater"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverar …"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Det går inte att ändra ljusstyrkan eftersom den styrs av den översta appen"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotera automatiskt"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotera skärmen automatiskt"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Plats"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgivningsläge"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vänster"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Höger"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Utöka till kontroller till vänster och höger"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Komprimera till enhetlig kontroll"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Stäng av omgivningsljudet"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Slå på omgivningsljudet"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Verktyg"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Live Caption"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Anteckning"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vill du återaktivera enhetens mikrofon?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vill du återaktivera enhetens kamera?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Använd delad skärm med appen till höger"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Använd delad skärm med appen till vänster"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Använd helskärm"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Använd datorvyn"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Byt till appen till höger eller nedanför när du använder delad skärm"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Byt till appen till vänster eller ovanför när du använder delad skärm"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Med delad skärm: ersätt en app med en annan"</string>
@@ -1000,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lägg till på position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen är ogiltig."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Kortet har redan lagts till"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kortet har lagts till"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kortet har tagits bort"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigerare för snabbinställningar."</string>
@@ -1568,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Okänt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vill du återställa alla rutor?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alla Snabbinställningsrutor återställs till enhetens ursprungliga inställningar"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 6222a7631fa6..b7d208a95d85 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Imeunganishwa kwenye <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Imeunganishwa kwenye <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Panua kikundi."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Weka kifaa kwenye kikundi."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Ondoa kifaa kwenye kikundi."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Fungua programu."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Haijaunganishwa."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Mitandao ya ng\'ambo"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vifaa vya kuingiza sauti"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Visaidizi vya kusikia"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Inawasha..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Imeshindwa kurekebisha mwangaza kwa sababu inadhibitiwa na programu iliyo juu"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Zungusha kiotomatiki"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Skrini ijizungushe kiotomatiki"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Mahali"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Mazingira"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kushoto"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kulia"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Panua iwe vidhibiti vilivyotenganishwa kushoto na kulia"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Kunja iwe kidhibiti cha pamoja"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Zima sauti ya mazingira"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Rejesha sauti ya mazingira"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Zana"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Manukuu Papo Hapo"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Dokezo"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Ungependa kuwacha kuzuia maikrofoni ya kifaa?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Ungependa kuacha kuzuia kamera ya kifaa?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Tumia hali ya kugawa skrini na programu ya sasa iwe upande wa kulia"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Tumia hali ya kugawa skrini na programu ya sasa iwe upande wa kushoto"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Tumia skrini nzima"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Tumia mwonekano wa kompyuta"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Badilisha ili uende kwenye programu iliyo kulia au chini unapotumia hali ya kugawa skrini"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Badilisha uende kwenye programu iliyo kushoto au juu unapotumia hali ya kugawa skrini"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ukigawanya skrini: badilisha kutoka programu moja hadi nyingine"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Onyesha aikoni za arifa zisizo muhimu"</string>
<string name="other" msgid="429768510980739978">"Nyingine"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ubadilishe ukubwa wa kigae"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ondoa kigae"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"weka kigae kwenye nafasi ya mwisho"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Hamisha kigae"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ongeza kwenye nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nafasi si sahihi."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kigae kimewekwa"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kigae kimeondolewa"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kihariri cha Mipangilio ya haraka."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Telezesha vidole vitatu juu na ushikilie kwenye padi ya kugusa ili uangalie programu za hivi majuzi"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Bonyeza kitufe cha vitendo kwenye kibodi yako ili uangalie programu zako zote"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Maandishi yameondolewa"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Fungua ili uone"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Fungua ili uone msimbo"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Elimu inayolingana na muktadha"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Kutumia padi yako ya kugusa ili kurudi nyuma"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Telezesha vidole vitatu kulia au kushoto. Gusa ili upate maelezo kuhusu miguso zaidi."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Visivyojulikana"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Ungependa kubadilisha vigae vyote?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Vigae vyote vya Mipangilio ya Haraka vitabadilishwa kuwa katika mipangilio halisi ya kifaa"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
index fb6e38f5b58c..a16a7b7792c2 100644
--- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
@@ -68,7 +68,7 @@
</string-array>
<string-array name="tile_states_bt">
<item msgid="5330252067413512277">"Hakipatikani"</item>
- <item msgid="5315121904534729843">"Kimezimwa"</item>
+ <item msgid="5315121904534729843">"Imezimwa"</item>
<item msgid="503679232285959074">"Imewashwa"</item>
</string-array>
<string-array name="tile_states_airplane">
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 41bb37efa623..f4f0424ade98 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -30,11 +30,6 @@
not appear immediately after user swipes to the side -->
<dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
- <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
- <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
- <dimen name="qs_media_rec_album_size">112dp</dimen>
- <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
-
<dimen name="controls_panel_corner_radius">40dp</dimen>
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 0bf5f223d139..473be6f14093 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>க்கு இணைக்கப்பட்டது."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> உடன் இணைக்கப்பட்டுள்ளது."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"குழுவை விரிவாக்கும்."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"குழுவில் சாதனத்தைச் சேர்க்கும்."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"குழுவிலிருந்து சாதனத்தை அகற்றும்."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ஆப்ஸைத் திறக்கும்."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"இணைக்கப்படவில்லை."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ரோமிங்"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"உள்ளீடு"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"செவித்துணைக் கருவி"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ஆன் செய்கிறது…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ஒளிர்வை மேலுள்ள ஆப்ஸ் கட்டுப்படுத்துவதால் அதை மாற்ற முடியவில்லை"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"தானாகச் சுழற்று"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"திரையைத் தானாகச் சுழற்று"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"இருப்பிடம்"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"சுற்றுப்புறங்கள்"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"இடது"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"வலது"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"இடது மற்றும் வலதுபுறம் உள்ள கட்டுப்பாடுகளை விரிவாக்கும்"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ஒருங்கிணைந்த கட்டுப்பாட்டுக்குச் சுருக்கும்"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"சுற்றுப்புறங்களின் ஒலியை அடக்கும்"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"சுற்றுப்புறங்களின் ஒலியை இயக்கும்"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"கருவிகள்"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"உடனடி வசனம்"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"குறிப்பு"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"சாதனத்தின் மைக்ரோஃபோனுக்கான தடுப்பை நீக்கவா?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"சாதனத்தின் கேமராவுக்கான தடுப்பை நீக்கவா?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ஆப்ஸ் வலதுபுறம் வரும்படி திரைப் பிரிப்பைப் பயன்படுத்துதல்"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ஆப்ஸ் இடதுபுறம் வரும்படி திரைப் பிரிப்பைப் பயன்படுத்துதல்"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"முழுத்திரையைப் பயன்படுத்து"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"டெஸ்க்டாப் காட்சியைப் பயன்படுத்து"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"திரைப் பிரிப்பைப் பயன்படுத்தும்போது வலது/கீழ் உள்ள ஆப்ஸுக்கு மாறுதல்"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"திரைப் பிரிப்பைப் பயன்படுத்தும்போது இடது/மேலே உள்ள ஆப்ஸுக்கு மாறுதல்"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"திரைப் பிரிப்பின்போது: ஓர் ஆப்ஸுக்குப் பதிலாக மற்றொன்றை மாற்றுதல்"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"குறைந்த முன்னுரிமை உள்ள அறிவிப்பு ஐகான்களைக் காட்டு"</string>
<string name="other" msgid="429768510980739978">"மற்றவை"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"கட்டத்தின் அளவை நிலைமாற்றும்"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"கட்டத்தை அகற்றும்"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"கடைசி இடத்தில் கட்டத்தைச் சேர்க்கலாம்"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"கட்டத்தை நகர்த்து"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>ல் சேர்க்கும்"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"நிலை தவறானது."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"இடம்: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"கட்டம் சேர்க்கப்பட்டது"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"கட்டம் அகற்றப்பட்டது"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"விரைவு அமைப்புகள் திருத்தி."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"சமீபத்திய ஆப்ஸைப் பார்க்க, டச்பேடில் மூன்று விரல்களால் மேல்நோக்கி ஸ்வைப் செய்து பிடிக்கவும்"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"அனைத்து ஆப்ஸையும் பார்க்க, உங்கள் கீபோர்டில் உள்ள ஆக்ஷன் பட்டனை அழுத்தவும்"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"அர்த்தம் புரியாதபடி திருத்தப்பட்டது"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"பார்ப்பதற்கு அன்லாக் செய்யவும்"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"குறியீட்டைப் பார்க்க அன்லாக் செய்யவும்"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"சூழல் சார்ந்த கல்வி"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"பின்செல்ல, உங்கள் டச்பேடைப் பயன்படுத்துங்கள்"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"மூன்று விரல்களால் இடது அல்லது வலதுபுறம் ஸ்வைப் செய்யவும். சைகைகள் குறித்து மேலும் அறிய தட்டவும்."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"தெரியவில்லை"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"அனைத்துக் கட்டங்களையும் மீட்டமைக்கவா?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"விரைவு அமைப்புகளின் கட்டங்கள் அனைத்தும் சாதனத்தின் அசல் அமைப்புகளுக்கு மீட்டமைக்கப்படும்"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 2042ead675d6..a75dd225bb24 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"గ్రూప్‌ను విస్తరించండి."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"గ్రూప్‌కి పరికరాన్ని జోడించండి."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"గ్రూప్ నుండి పరికరాన్ని తీసివేయండి."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"యాప్‌ను తెరవండి."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"కనెక్ట్ చేయబడలేదు."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"రోమింగ్"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ఇన్‌పుట్"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"వినికిడి పరికరాలు"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ఆన్ చేస్తోంది…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"దీనిని టాప్ యాప్ కంట్రోల్ చేస్తోంది కనుక బ్రైట్‌నెస్‌ను సర్దుబాటు చేయడం సాధ్యం కాదు"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ఆటో-రొటేట్‌"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"స్క్రీన్ ఆటో-రొటేట్‌"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"లొకేషన్"</string>
@@ -426,12 +423,16 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"పరిసరాలు"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ఎడమ వైపునకు"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"కుడి వైపునకు"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"పరిసరాలు"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"ఎడమ వైపు పరిసరాలు"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"కుడి వైపు పరిసరాలు"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ఎడమ, కుడి అని వేరు చేయబడిన కంట్రోల్స్‌కు విస్తరించండి"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"యూనిఫైడ్ కంట్రోల్‌కు కుదించండి"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"పరిసరాలను మ్యూట్ చేయండి"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"పరిసరాలను అన్‌మ్యూట్ చేయండి"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"టూల్స్"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"లైవ్ క్యాప్షన్"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"సెట్టింగ్‌లు"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"గమనిక"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"పరికరం మైక్రోఫోన్‌ను అన్‌బ్లాక్ చేయమంటారా?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"పరికరంలోని కెమెరాను అన్‌బ్లాక్ చేయమంటారా?"</string>
@@ -904,7 +905,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"కుడి వైపు ప్రస్తుత యాప్‌తో స్ప్లిట్ స్క్రీన్‌ను ఉపయోగించండి"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ఎడమ వైపు ప్రస్తుత యాప్‌తో స్ప్లిట్ స్క్రీన్‌ను ఉపయోగించండి"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ఫుల్ స్క్రీన్‌ను ఉపయోగించండి"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"డెస్క్‌టాప్ వీక్షణను ఉపయోగించండి"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"స్ప్లిట్ స్క్రీన్ ఉపయోగిస్తున్నప్పుడు కుడి లేదా కింద యాప్‌నకు మారండి"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"స్ప్లిట్ స్క్రీన్ ఉపయోగిస్తున్నప్పుడు ఎడమ లేదా పైన యాప్‌నకు మారండి"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"స్ప్లిట్ స్క్రీన్ సమయంలో: ఒక దాన్నుండి మరో దానికి యాప్ రీప్లేస్ చేయండి"</string>
@@ -1001,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> స్థానానికి జోడించండి"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ప్రస్తుత పొజిషన్ చెల్లదు."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"స్థానం <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"టైల్‌ను ఇప్పటికే జోడించారు"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"టైల్ జోడించబడింది"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"టైల్ తీసివేయబడింది"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"శీఘ్ర సెట్టింగ్‌ల ఎడిటర్."</string>
@@ -1571,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"తెలియదు"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"టైల్స్ అన్ని రీసెట్ చేయాలా?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"అన్ని క్విక్ సెట్టింగ్‌ల టైల్స్, పరికరం తాలూకు ఒరిజినల్ సెట్టింగ్‌లకు రీసెట్ చేయబడతాయి"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 9ac9d01b8ee7..e1413f2ebe65 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"เชื่อมต่อกับ <xliff:g id="BLUETOOTH">%s</xliff:g> แล้ว"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"เชื่อมต่อกับ <xliff:g id="CAST">%s</xliff:g>"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ขยายกลุ่ม"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"เพิ่มอุปกรณ์ลงในกลุ่ม"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"นำอุปกรณ์ออกจากกลุ่ม"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"เปิดแอปพลิเคชัน"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ไม่ได้เชื่อมต่อ"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"โรมมิ่ง"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"อินพุต"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"เครื่องช่วยฟัง"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"กำลังเปิด..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ปรับความสว่างไม่ได้เนื่องจากควบคุมโดยแอปที่อยู่ด้านบน"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"หมุนอัตโนมัติ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"หมุนหน้าจออัตโนมัติ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ตำแหน่ง"</string>
@@ -426,12 +423,17 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"เสียงแวดล้อม"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ซ้าย"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ขวา"</string>
+ <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"เสียงแวดล้อม"</string>
+ <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"เสียงแวดล้อมด้านซ้าย"</string>
+ <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"เสียงแวดล้อมด้านขวา"</string>
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ขยายเป็นการควบคุมที่แยกด้านซ้ายและขวา"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ยุบเป็นการควบคุมแบบรวม"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ปิดเสียงแวดล้อม"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"เปิดเสียงแวดล้อม"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"เครื่องมือ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"คำบรรยายสด"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"จดบันทึก"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"เลิกบล็อกไมโครโฟนของอุปกรณ์ใช่ไหม"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"เลิกบล็อกกล้องของอุปกรณ์ใช่ไหม"</string>
@@ -904,7 +906,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"ใช้โหมดแยกหน้าจอโดยให้แอปอยู่ด้านขวา"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"ใช้โหมดแยกหน้าจอโดยให้แอปอยู่ด้านซ้าย"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"ใช้โหมดเต็มหน้าจอ"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ใช้มุมมองบนเดสก์ท็อป"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"เปลี่ยนไปใช้แอปทางด้านขวาหรือด้านล่างขณะใช้โหมดแยกหน้าจอ"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"เปลี่ยนไปใช้แอปทางด้านซ้ายหรือด้านบนขณะใช้โหมดแยกหน้าจอ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ระหว่างใช้โหมดแยกหน้าจอ: เปลี่ยนแอปหนึ่งเป็นอีกแอปหนึ่ง"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"แสดงไอคอนการแจ้งเตือนลำดับความสำคัญต่ำ"</string>
<string name="other" msgid="429768510980739978">"อื่นๆ"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"สลับขนาดของการ์ด"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"นำชิ้นส่วนออก"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"เพิ่มการ์ดไปยังตำแหน่งสุดท้าย"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ย้ายชิ้นส่วน"</string>
@@ -1001,6 +1003,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"เพิ่มไปยังตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ตำแหน่งไม่ถูกต้อง"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"เพิ่มการ์ดแล้ว"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"เพิ่มชิ้นส่วนแล้ว"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"นำชิ้นส่วนออกแล้ว"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ตัวแก้ไขการตั้งค่าด่วน"</string>
@@ -1545,10 +1548,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"หากต้องการดูแอปล่าสุด ให้ใช้ 3 นิ้วปัดขึ้นแล้วค้างไว้บนทัชแพด"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"หากต้องการดูแอปทั้งหมด ให้กดปุ่มดำเนินการบนแป้นพิมพ์"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"ปกปิดไว้"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ปลดล็อกเพื่อดู"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ปลดล็อกเพื่อดูรหัส"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"การศึกษาตามบริบท"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ใช้ทัชแพดเพื่อย้อนกลับ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ใช้ 3 นิ้วปัดไปทางซ้ายหรือขวา แตะเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับท่าทางสัมผัสต่างๆ"</string>
@@ -1571,4 +1572,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ไม่ทราบ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"รีเซ็ตการ์ดทั้งหมดใช่ไหม"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"การ์ดการตั้งค่าด่วนทั้งหมดจะรีเซ็ตเป็นการตั้งค่าเดิมของอุปกรณ์"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 516386bd3c9b..aff1621dd4cf 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Nakakonekta sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Nakakonekta sa <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"I-expand ang grupo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Idagdag ang device sa grupo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Alisin ang device sa grupo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Buksan ang application."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Hindi nakakonekta."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Mga hearing aid"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ino-on…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Hindi ma-adjust ang liwanag dahil kinokontrol ito ng nangingibabaw na app"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"I-auto rotate"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Awtomatikong i-rotate ang screen"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasyon"</string>
@@ -426,12 +423,19 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Paligid"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kaliwa"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kanan"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"I-expand sa kaliwa at kanang magkahiwalay na mga kontrol"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"I-collapse sa pinag-isang kontrol"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"I-mute ang paligid"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"I-unmute ang paligid"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Mga Tool"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Instant Caption"</string>
+ <string name="hearing_devices_settings_button" msgid="999474385481812222">"Mga Setting"</string>
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Tala"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"I-unblock ang mikropono ng device?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"I-unblock ang camera ng device?"</string>
@@ -904,7 +908,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Gumamit ng split screen nang nasa kanan ang app"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Gumamit ng split screen nang nasa kaliwa ang app"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Gamitin ang full screen"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Gamitin ang desktop view"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Lumipat sa app sa kanan o ibaba habang ginagamit ang split screen"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Lumipat sa app sa kaliwa o itaas habang ginagamit ang split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Habang nasa split screen: magpalit-palit ng app"</string>
@@ -1000,6 +1005,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Idagdag sa posisyong <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Invalid ang posisyon."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisyon <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Naidagdag na ang tile"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Idinagdag ang tile"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Inalis ang tile"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor ng Mga mabilisang setting."</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Hindi Alam"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"I-reset ang lahat ng tile?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Magre-reset sa mga orihinal na setting ng device ang lahat ng tile ng Mga Mabilisang Setting"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index b6432030aa01..eb5b95143af7 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ile bağlı."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> bağlantısı kuruldu."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Grubu genişlet."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Cihazı gruba ekle."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Cihazı gruptan kaldır."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Uygulama aç."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Bağlanmadı."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Dolaşım"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"İşitme cihazları"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Açılıyor…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Parlaklık ayarlanamıyor, çünkü bu özellik en üstteki uygulama tarafından kontrol ediliyor"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Otomatik döndür"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranı otomatik döndür"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Konum"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Çevredeki sesler"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Sol"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Sağ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Sol ve sağ kontrolleri ayırarak genişlet"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Kontrolleri birleştirerek daralt"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Çevredeki sesleri kapat"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Çevredeki sesleri aç"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Araçlar"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Canlı Altyazı"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Not"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Cihaz mikrofonunun engellemesi kaldırılsın mı?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Cihaz kamerasının engellemesi kaldırılsın mı?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Sağdaki uygulamayla birlikte bölünmüş ekranı kullan"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Soldaki uygulamayla birlikte bölünmüş ekranı kullan"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Tam ekran kullanın"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Masaüstü görünümünü kullanın"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Bölünmüş ekran kullanırken sağdaki veya alttaki uygulamaya geçiş yap"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bölünmüş ekran kullanırken soldaki veya üstteki uygulamaya geçiş yapın"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Bölünmüş ekran etkinken: Bir uygulamayı başkasıyla değiştir"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Düşük öncelikli bildirim simgelerini göster"</string>
<string name="other" msgid="429768510980739978">"Diğer"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"kutu boyutunu değiştir"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"Kutuyu kaldırmak için"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"kutuyu son konuma ekleyin"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Kutuyu taşı"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna ekle"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Konum geçersiz."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Konum: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kutu eklendi"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kutu kaldırıldı"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Hızlı ayar düzenleyicisi."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Son uygulamaları görüntülemek için dokunmatik alanda üç parmağınızla yukarı kaydırıp basılı tutun"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Tüm uygulamalarınızı görüntülemek için klavyenizdeki eylem tuşuna basın"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Çıkartıldı"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Görüntülemek için kilidi açın"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kodu görüntülemek için kilidi açın"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Bağlama dayalı eğitim"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Geri dönmek için dokunmatik alanınızı kullanın"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Üç parmağınızla sola veya sağa kaydırın. Daha fazla hareket öğrenmek için dokunun."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Bilinmiyor"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Tüm ayar kutuları sıfırlansın mı?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tüm Hızlı Ayarlar kutuları cihazın özgün ayarlarına sıfırlanır"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index db3c02da8fcc..1f5d0aac98c6 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -247,15 +247,13 @@
<string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Натисніть, щоб змінити налаштування пристрою"</string>
<string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"<xliff:g id="DEVICE_NAME">%s</xliff:g>. Змінити налаштування пристрою"</string>
<string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"Переглянути всі пристрої"</string>
- <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"Підключити новий пристрій"</string>
+ <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"Зв’язати новий пристрій"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Підключено до <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Під’єднано до пристрою <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Розгорнути групу"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Додати пристрій у групу."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Вилучити пристрій із групи."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Відкрити додаток"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Не з’єднано."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роумінг"</string>
@@ -308,7 +306,7 @@
<string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
<string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Немає спарених пристроїв"</string>
<string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Натисніть, щоб під’єднати або від’єднати пристрій"</string>
- <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Підключити новий пристрій"</string>
+ <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Зв’язати новий пристрій"</string>
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Показати всі"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Джерело сигналу"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухові апарати"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Увімкнення…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не вдається змінити яскравість, оскільки нею керує основний додаток"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автообертання"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично обертати екран"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Геодані"</string>
@@ -418,7 +415,7 @@
<string name="quick_settings_hearing_devices_connected" msgid="6519069502397037781">"Під’єднано"</string>
<string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Від’єднано"</string>
<string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Слухові апарати"</string>
- <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Підключити новий пристрій"</string>
+ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Зв’язати новий пристрій"</string>
<string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Натисніть, щоб підключити новий пристрій"</string>
<string name="hearing_devices_presets_error" msgid="350363093458408536">"Не вдалось оновити набір налаштувань"</string>
<string name="hearing_devices_preset_label" msgid="7878267405046232358">"Набір налаштувань"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Звуки оточення"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ліворуч"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Праворуч"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Розгорнути в окремі елементи керування ліворуч і праворуч"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Згорнути в єдиний елемент керування"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Вимкнути звуки оточення"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Увімкнути звуки оточення"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Інструменти"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Живі субтитри"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Нотатка"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Надати доступ до мікрофона?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Надати доступ до камери пристрою?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Розділити екран і показувати додаток праворуч"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Розділити екран і показувати додаток ліворуч"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Використовувати повноекранний режим"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Використовувати версію для комп’ютера"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Перейти до додатка праворуч або внизу на розділеному екрані"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Під час розділення екрана перемикатися на додаток ліворуч або вгорі"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Під час розділення екрана: замінити додаток іншим"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показувати значки сповіщень із низьким пріоритетом"</string>
<string name="other" msgid="429768510980739978">"Інше"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"змінити розмір плитки"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"вилучити опцію"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"додати панель на останню позицію"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перемістити опцію"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додати на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позиція недійсна."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиція <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Опцію додано"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Опцію вилучено"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор швидких налаштувань."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Щоб переглянути останні додатки, проведіть трьома пальцями вгору по сенсорній панелі й утримуйте їх"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Щоб переглянути всі додатки, натисніть клавішу дії на клавіатурі"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Замасковано"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Розблокуйте, щоб переглянути"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Розблокуйте, щоб переглянути код"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контекстне навчання"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Щоб повернутися, використовуйте сенсорну панель"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Проведіть трьома пальцями вліво чи вправо. Натисніть, щоб дізнатися про інші жести."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Невідомо"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Скинути всі панелі?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Усі панелі швидких налаштувань буде скинуто до стандартних налаштувань пристрою"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ffc3f5e7c8aa..bcaa91240d53 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> سے منسلک ہیں۔"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> سے منسلک ہے۔"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"گروپ کو پھیلائیں۔"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"آلہ کو گروپ میں شامل کریں۔"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"آلہ کو گروپ سے ہٹائیں۔"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ایپلیکیشن کھولیں۔"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"مربوط نہیں ہے۔"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"رومنگ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ان پٹ"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعتی آلات"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"آن ہو رہا ہے…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"چمک کو ایڈجسٹ نہیں کیا جا سکتا کیونکہ اسے سرفہرست ایپ کے ذریعے کنٹرول کیا جا رہا ہے"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"خود کار طور پر گھمائیں"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"اسکرین کو خود کار طور پر گھمائیں"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"مقام"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"اطراف"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"دائیں"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"بائیں"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"بائیں اور دائیں علیحدہ کردہ کنٹرولز کو پھیلائیں"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"یونیفائیڈ کنٹرول کیلئے سکیڑیں"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"اطراف کو خاموش کریں"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"اطراف کی آواز چالو کریں"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"ٹولز"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"لائیو کیپشن"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"نوٹ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"آلے کا مائیکروفون غیر مسدود کریں؟"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"آلے کا کیمرا غیر مسدود کریں؟"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"بائیں جانب ایپ کے ساتھ اسپلٹ اسکرین کا استعمال کریں"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"دائیں جانب ایپ کے ساتھ اسپلٹ اسکرین کا استعمال کریں"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"فُل اسکرین استعمال کریں"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"ڈیسک ٹاپ منظر استعمال کریں"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"اسپلٹ اسکرین کا استعمال کرتے ہوئے دائیں یا نیچے ایپ پر سوئچ کریں"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"اسپلٹ اسکرین کا استعمال کرتے ہوئے بائیں یا اوپر ایپ پر سوئچ کریں"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"اسپلٹ اسکرین کے دوران: ایک ایپ کو دوسرے سے تبدیل کریں"</string>
@@ -1000,6 +1006,7 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g> میں شامل کریں"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"پوزیشن غلط ہے۔"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"ٹائل پہلے ہی شامل کر دیا گیا ہے"</string>
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ٹائل کو شامل کیا گیا"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ٹائل کو ہٹا دیا گیا"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"فوری ترتیبات کا ایڈیٹر۔"</string>
@@ -1568,4 +1575,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"نامعلوم"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"سبھی ٹائلز ری سیٹ کریں؟"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"سبھی فوری ترتیبات کی ٹائلز آلہ کی اصل ترتیبات پر ری سیٹ ہو جائیں گی"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>، <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e325cf33adb3..bf070b5535bd 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ulangan: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Bunga ulangan: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Guruhni yoying."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Qurilmani guruhga kiritish."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Qurilmani guruhdan olib tashlash."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Ilovani oching."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Ulanmagan."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Rouming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Kirish"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eshitish moslamalari"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Yoqilmoqda…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Yorqinlikni sozlash imkonsiz, chunki tepadago ilova tomonidan boshqariladi"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avto-burilish"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranning avtomatik burilishi"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Joylashuv"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Atrof-muhit"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Chap"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Oʻng"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Chap va oʻngga ajratilgan boshqaruv elementlariga yoyish"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Yagona boshqaruvga yigʻish"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Atrof-muhitni ovozsiz qilish"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Atrof-muhitni sukutdan chiqarish"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Vositalar"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Jonli izoh"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Qayd"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Qurilma mikrofoni blokdan chiqarilsinmi?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Qurilma kamerasi blokdan chiqarilsinmi?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Ekranni ajratib, joriy ilovani oʻngga joylash"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Ekranni ajratib, joriy ilovani chapga joylash"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Butun ekrandan foydalanish"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Desktop versiyadan foydalanish"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Ajratilgan ekranda oʻngdagi yoki pastdagi ilovaga almashish"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Ajratilgan ekranda chapdagi yoki yuqoridagi ilovaga almashish"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ajratilgan rejimda ilovalarni oʻzaro almashtirish"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Muhim boʻlmagan bildirishnoma ikonkalarini koʻrsatish"</string>
<string name="other" msgid="429768510980739978">"Boshqa"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"katak oʻlchamini almashtirish"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"katakchani olib tashlash"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"kartochkani oxirgi oʻringa qoʻshish"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Katakchani boshqa joyga olish"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bu joyga kiritish: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozitsiya yaroqsiz."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Joylashuv: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Katakcha kiritildi"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Katakcha olib tashlandi"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Tezkor sozlamalar muharriri"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Oxirgi ilovalarni koʻrish uchun sensorli panelda uchta barmoq bilan tepaga surib, bosib turing"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Barcha ishoralarni koʻrish uchun klaviaturadagi amal tugmasini bosing"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Chiqarildi"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Koʻrish uchun qulfdan chiqaring"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kodni koʻrish uchun qulfdan chiqaring"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstual taʼlim"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Sensorli panel orqali orqaga qaytish"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Uchta barmoq bilan chapga yoki oʻngga suring. Boshqa ishoralar bilan tanishish uchun bosing."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Noaniq"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Barcha katakchalar asliga qaytarilsinmi?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Barcha Tezkor sozlamalar katakchalari qurilmaning asl sozlamalariga qaytariladi"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index f64f0843304b..9e11f3813842 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Đã kết nối với <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Đã kết nối với <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Mở rộng nhóm."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Thêm thiết bị vào nhóm."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Xoá thiết bị khỏi nhóm."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Mở ứng dụng."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Chưa được kết nối."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Chuyển vùng"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Thiết bị đầu vào"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Thiết bị trợ thính"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Đang bật…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Không điều chỉnh được độ sáng do ứng dụng ở trên cùng đang điều khiển độ sáng"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Tự động xoay"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Tự động xoay màn hình"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Vị trí"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Âm lượng xung quanh"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Trái"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Phải"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Mở rộng thành các nút điều khiển tách biệt bên trái và bên phải"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Thu gọn thành nút điều khiển hợp nhất"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Tắt tiếng xung quanh"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Bật tiếng xung quanh"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Công cụ"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Phụ đề trực tiếp"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Ghi chú"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Bỏ chặn micrô của thiết bị?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Bỏ chặn camera của thiết bị?"</string>
@@ -586,7 +591,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"Truyền một ứng dụng"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"Truyền toàn bộ màn hình"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Khi bạn truyền toàn bộ màn hình thì người khác sẽ thấy được mọi nội dung trên màn hình của bạn. Vì vậy, hãy thận trọng đối với những thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Khi bạn truyền một ứng dụng, thì người khác sẽ thấy được mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng đối với những thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Khi bạn truyền một ứng dụng, mọi nội dung xuất hiện hoặc phát trên ứng dụng đó đều hiển thị trên thiết bị được truyền tới. Vì vậy, hãy thận trọng đối với những thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Màn hình truyền"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Chọn ứng dụng để truyền"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Bắt đầu chia sẻ?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Dùng tính năng chia đôi màn hình với ứng dụng ở bên phải"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Dùng tính năng chia đôi màn hình với ứng dụng ở bên trái"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Dùng chế độ toàn màn hình"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Dùng chế độ xem trên máy tính để bàn"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Chuyển sang ứng dụng bên phải hoặc ở dưới khi đang chia đôi màn hình"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Chuyển sang ứng dụng bên trái hoặc ở trên khi đang chia đôi màn hình"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Trong chế độ chia đôi màn hình: thay một ứng dụng bằng ứng dụng khác"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Hiển thị biểu tượng thông báo có mức ưu tiên thấp"</string>
<string name="other" msgid="429768510980739978">"Khác"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"bật hoặc tắt kích thước của ô"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"xóa ô"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"thêm ô vào vị trí cuối cùng"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Di chuyển ô"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Thêm vào vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Vị trí không hợp lệ."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Đã thêm thẻ thông tin"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Đã xóa thẻ thông tin"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Trình chỉnh sửa cài đặt nhanh."</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Để xem các ứng dụng gần đây, hãy dùng 3 ngón tay vuốt lên và giữ trên bàn di chuột"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Để xem tất cả ứng dụng của bạn, hãy nhấn phím hành động trên bàn phím"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Bị loại bỏ"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Mở khoá để xem"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Mở khoá để xem mã"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Hướng dẫn theo bối cảnh"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Dùng bàn di chuột để quay lại"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Dùng 3 ngón tay vuốt sang trái hoặc sang phải. Hãy nhấn để tìm hiểu các cử chỉ khác."</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Không xác định"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Đặt lại mọi ô?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Mọi ô Cài đặt nhanh sẽ được đặt lại về chế độ cài đặt ban đầu của thiết bị"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index a355a678cfc9..cbc4e5183f43 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -150,7 +150,7 @@
<string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"停止投屏吗?"</string>
<string name="cast_to_other_device_stop_dialog_message_entire_screen_with_device" msgid="1474703115926205251">"您正在将整个屏幕投放到“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”上"</string>
<string name="cast_to_other_device_stop_dialog_message_entire_screen" msgid="8419219169553867625">"您正在将整个屏幕投放到附近的设备上"</string>
- <string name="cast_to_other_device_stop_dialog_message_specific_app_with_device" msgid="2715934698604085519">"您正在将“<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>”投放到“<xliff:g id="DEVICE_NAME">%2$s</xliff:g>”上"</string>
+ <string name="cast_to_other_device_stop_dialog_message_specific_app_with_device" msgid="2715934698604085519">"目前正在将“<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>”投放到“<xliff:g id="DEVICE_NAME">%2$s</xliff:g>”"</string>
<string name="cast_to_other_device_stop_dialog_message_specific_app" msgid="8616103075630934513">"您正在将“<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>”投放到附近的设备上"</string>
<string name="cast_to_other_device_stop_dialog_message_generic_with_device" msgid="9213582497852420203">"您正在向“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”上投放内容"</string>
<string name="cast_to_other_device_stop_dialog_message_generic" msgid="4100272100480415076">"您正在向附近的设备投放内容"</string>
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已连接到<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"已连接到 <xliff:g id="CAST">%s</xliff:g>。"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"展开群组。"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"将设备添加到群组。"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"从群组中移除设备。"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"打开应用。"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"未连接。"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"漫游"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"输入"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助听器"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在开启…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"亮度无法调整,因为它正在被顶层应用控制"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自动屏幕旋转"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自动旋转屏幕"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"位置信息"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"周围声音"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左侧"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右侧"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"展开为左侧和右侧的单独控件"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"收起为统一控件"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"将周围声音静音"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"取消周围声音静音"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"工具"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"实时字幕"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"记事"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"要解锁设备麦克风吗?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"要解锁设备摄像头吗?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"使用分屏模式,并将应用置于右侧"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"使用分屏模式,并将应用置于左侧"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"使用全屏"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"使用桌面版视图"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"使用分屏模式时,切换到右侧或下方的应用"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分屏模式时,切换到左侧或上方的应用"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"在分屏期间:将一个应用替换为另一个应用"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"显示低优先级的通知图标"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"切换功能块大小"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除功能块"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"将功能块添加到最后一个位置"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移动功能块"</string>
@@ -1001,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"添加到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置无效。"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已添加功能块"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除功能块"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快捷设置编辑器。"</string>
@@ -1545,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"如要查看最近用过的应用,请用三根手指在触控板上向上滑动并按住"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"如要查看所有应用,请按下键盘上的快捷操作按键"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"已隐去"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"解锁即可查看"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"解锁即可查看验证码"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"内容相关指导"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"使用触控板返回"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"用三根手指向左或向右滑动。点按即可了解更多手势。"</string>
@@ -1571,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"未知"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重置所有功能块吗?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有“快捷设置”功能块都将重置为设备的原始设置"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>,<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index c5a81fb25a9b..0e1de31e0a08 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"已連接至 <xliff:g id="CAST">%s</xliff:g>。"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"展開群組。"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"將裝置新增至群組。"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"從群組移除裝置。"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"開啟應用程式。"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"未連線。"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"漫遊"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在開啟…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"無法調整亮度,因為目前是由上層應用程式控制亮度"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"位置"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"環境聲音"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"打開就可以分開左右控制"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"收埋就可以統一控制"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"環境聲音靜音"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"取消環境聲音靜音"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"工具"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"即時字幕"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"筆記"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"要解除封鎖裝置麥克風嗎?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"要解除封鎖裝置相機嗎?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"使用分割螢幕,並在右側顯示應用程式"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"使用分割螢幕,並在左側顯示應用程式"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"使用全螢幕"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"使用桌面電腦檢視模式"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"使用分割螢幕時,切換至右邊或下方的應用程式"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分割螢幕時,切換至左邊或上方的應用程式"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"使用分割螢幕期間:更換應用程式"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"加去位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置冇效。"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"加咗圖塊"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"移除咗圖塊"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快速設定編輯工具。"</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重設所有圖塊嗎?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有「快速設定」圖塊將重設為裝置的原始設定"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>,<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 24a15a14f1b3..a34333bcbc80 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"已連線至 <xliff:g id="CAST">%s</xliff:g>。"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"展開群組。"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"將裝置加入群組。"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"從群組中移除裝置。"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"開啟應用程式。"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"尚未連線。"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"漫遊"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"開啟中…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"無法調整亮度,因為目前是由上層應用程式控制亮度"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"定位"</string>
@@ -426,12 +423,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"環境"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"展開為左右獨立控制選項"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"收合為統合控制選項"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"將環境靜音"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"取消環境靜音"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"工具"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"即時字幕"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"筆記"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"要解除封鎖裝置麥克風嗎?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"解除封鎖裝置相機?"</string>
@@ -904,7 +909,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"使用分割畫面,並在右側顯示應用程式"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"使用分割畫面,並在左側顯示應用程式"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"使用全螢幕模式"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"使用電腦檢視畫面"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"使用分割畫面時,切換到右邊或上方的應用程式"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分割畫面時,切換到左邊或上方的應用程式"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"使用分割畫面期間:更換應用程式"</string>
@@ -1000,6 +1006,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"新增到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置無效。"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已新增設定方塊"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除設定方塊"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快速設定編輯器。"</string>
@@ -1568,4 +1576,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重設所有設定方塊嗎?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有快速設定方塊都會恢復裝置的原始設定"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>、<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 918864886d21..7d43adc83376 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -254,10 +254,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Xhuma ku-<xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Ixhumeke ku-<xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Nweba iqembu."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Faka idivayisi eqenjini."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Susa idivayisi eqenjini."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Vula i-application."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Akuxhunyiwe"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Iyazulazula"</string>
@@ -335,8 +333,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Okokufaka"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Imishini yendlebe"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Iyavula..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ayikwazi ukulungisa ukukhanya ngoba ilawulwa yi-app ephezulu"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ukuphenduka okuzenzakalelayo"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Phendula iskrini ngokuzenzakalela"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Indawo"</string>
@@ -428,12 +425,20 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Izindawo ezizungezile"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kwesokunxele"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kwesokudla"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Nwebela ezilawulini ezihlukanisiwe zakwesokunxele nakwesokudla"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Goqa ezilawulini ezihlanganisiwe"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Thulisa izindawo ezizungezile"</string>
<string name="hearing_devices_ambient_unmute" msgid="2187938085943876814">"Susa ukuthula ezindaweni ezizungezile"</string>
<string name="hearing_devices_tools_label" msgid="1929081464316074476">"Amathuluzi"</string>
<string name="quick_settings_hearing_devices_live_caption_title" msgid="1054814050932225451">"Okushuthwe Bukhoma"</string>
+ <!-- no translation found for hearing_devices_settings_button (999474385481812222) -->
+ <skip />
<string name="quick_settings_notes_label" msgid="1028004078001002623">"Inothi"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vulela imakrofoni yedivayisi?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vulela ikhamera yedivayisi?"</string>
@@ -906,7 +911,8 @@
<string name="system_multitasking_rhs" msgid="8779289852395243004">"Sebenzisa ukuhlukanisa isikrini nge-app kwesokudla"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"Sebenzisa ukuhlukanisa isikrini nge-app kwesokunxele"</string>
<string name="system_multitasking_full_screen" msgid="4221409316059910349">"Sebenzisa isikrini esigcwele"</string>
- <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"Sebenzisa ukubuka kwedeskithophu"</string>
+ <!-- no translation found for system_multitasking_desktop_view (8871367687089347180) -->
+ <skip />
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"Shintshela ku-app ngakwesokudla noma ngezansi ngenkathi usebenzisa uhlukanisa isikrini"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Shintshela ku-app ngakwesokunxele noma ngaphezulu ngenkathi usebenzisa ukuhlukanisa isikrini"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ngesikhathi sokuhlukaniswa kwesikrini: shintsha i-app ngenye"</string>
@@ -993,8 +999,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Bonisa izithonjana zesaziso zokubaluleka okuncane"</string>
<string name="other" msgid="429768510980739978">"Okunye"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"guqula usayizi wethayela"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"susa ithayela"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"faka ithayela endaweni yokugcina"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Hambisa ithayela"</string>
@@ -1003,6 +1008,8 @@
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engeza kusikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Indawo ayivumelekile."</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Isikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ithayela lingeziwe"</string>
<string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ithayela likhishiwe"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Isihleli sezilungiselelo ezisheshayo."</string>
@@ -1547,10 +1554,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Ukuze ubuke ama-app akamuva, swayiphela phezulu bese ubambe ngeminwe emithathu ephedini yokuthinta"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Ukuze ubuke wonke ama-app wakho, cindezela inkinobho yokufinyelela kukhibhodi yakho"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Kwenziwe iredact"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Vula ukuze ubuke"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Vula ukuze ubone ikhodi"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Imfundo yokuqukethwe"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Sebenzisa iphedi yokuthinta ukuze ubuyele emuva"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swayiphela kwesokunxele noma kwesokudla usebenzisa iminwe emithathu. Thepha ukuze ufunde kabanzi ngokunyakazisa umzimba."</string>
@@ -1573,4 +1578,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Akwaziwa"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Qala kabusha onke amathayela?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Ithayela Lamasethingi Asheshayo lizosetha kabusha libuyele kumasethingi okuqala edivayisi"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4995858f95a4..549fdefd8f7a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -98,10 +98,6 @@
TODO (b/293252410) - change this comment/resource when flag is enabled -->
<integer name="small_land_lockscreen_quick_settings_max_rows">2</integer>
- <!-- If the dp width of the available space is <= this value, potentially adjust the number
- of media recommendation items-->
- <integer name="default_qs_media_rec_width_dp">380</integer>
-
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<!-- The default tiles to display in QuickSettings -->
@@ -119,7 +115,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects
+ internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7c370d3bc064..d0ae307b6919 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -283,7 +283,7 @@
<dimen name="notification_max_height">358dp</dimen>
<!-- Height of a large promoted ongoing notification in the status bar -->
- <dimen name="notification_max_height_for_promoted_ongoing">272dp</dimen>
+ <dimen name="notification_max_height_for_promoted_ongoing">218dp</dimen>
<!-- Height of a heads up notification in the status bar for legacy custom views -->
<dimen name="notification_max_heads_up_height_legacy">128dp</dimen>
@@ -1157,6 +1157,8 @@
<dimen name="smart_action_button_icon_size">18dp</dimen>
<dimen name="smart_action_button_icon_padding">8dp</dimen>
<dimen name="smart_action_button_outline_stroke_width">2dp</dimen>
+ <dimen name="notification_2025_smart_reply_button_corner_radius">18dp</dimen>
+ <dimen name="notification_2025_smart_reply_button_min_height">48dp</dimen>
<!-- Magic Action params. -->
<!-- Corner radius = half of min_height to create rounded sides. -->
@@ -1342,19 +1344,6 @@
<dimen name="qs_media_session_collapsed_legacy_guideline">144dp</dimen>
<dimen name="qs_media_session_collapsed_guideline">168dp</dimen>
- <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
- <dimen name="qs_media_rec_default_width">380dp</dimen>
- <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
- <dimen name="qs_media_rec_album_icon_size">16dp</dimen>
- <dimen name="qs_media_rec_album_size">88dp</dimen>
- <dimen name="qs_media_rec_album_width">110dp</dimen>
- <dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
- <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
- <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
- <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
- <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
- <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
-
<!-- Chipbar -->
<!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
<dimen name="chipbar_outer_padding">16dp</dimen>
@@ -2179,7 +2168,7 @@
<dimen name="volume_dialog_background_top_margin">-28dp</dimen>
<dimen name="volume_dialog_window_margin">14dp</dimen>
- <dimen name="volume_dialog_components_spacing">8dp</dimen>
+ <dimen name="volume_dialog_components_spacing">10dp</dimen>
<dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding_negative">
@@ -2202,11 +2191,6 @@
<dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
<dimen name="volume_dialog_ringer_drawer_margin">@dimen/volume_dialog_buttons_margin</dimen>
- <!--
- (volume_dialog_slider_width - volume_dialog_button_size) / 2
- This centers ringer drawer against the volume slider
- -->
- <dimen name="volume_dialog_ringer_drawer_diff_end_margin">6dp</dimen>
<dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen>
<dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
<dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 43ea2c3f7633..3fdb98b4e00b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1016,6 +1016,13 @@
<string name="hearing_devices_presets_error">Couldn\'t update preset</string>
<!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
<string name="hearing_devices_preset_label">Preset</string>
+ <!-- QuickSettings: Title for hearing aids input routing control in hearing device dialog. [CHAR LIMIT=40]-->
+ <string name="hearing_devices_input_routing_label">Default microphone for calls</string>
+ <!-- QuickSettings: Option for hearing aids input routing control in hearing device dialog. It will alter input routing for calls for hearing aid. [CHAR LIMIT=40]-->
+ <string-array name="hearing_device_input_routing_options">
+ <item>Hearing aid microphone</item>
+ <item>This phone\'s microphone</item>
+ </string-array>
<!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]-->
<string name="hearing_devices_spinner_item_selected">Selected</string>
<!-- QuickSettings: Title for ambient controls. [CHAR LIMIT=40]-->
@@ -1042,6 +1049,8 @@
<string name="hearing_devices_tools_label">Tools</string>
<!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
<string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
+ <!-- QuickSettings: Label for button to go to hearing devices settings page [CHAR_LIMIT=20] -->
+ <string name="hearing_devices_settings_button">Settings</string>
<!-- QuickSettings: Notes tile. The label of a quick settings tile for launching the default notes taking app. [CHAR LIMIT=NONE] -->
<string name="quick_settings_notes_label">Note</string>
@@ -2054,7 +2063,7 @@
<string name="inline_ok_button">Apply</string>
<!-- Notification inline controls: button to show block screen [CHAR_LIMIT=35] -->
- <string name="inline_turn_off_notifications">Turn off notifications</string>
+ <string name="inline_turn_off_notifications">Turn off</string>
<!-- [CHAR LIMIT=100] Notification Importance title -->
<string name="notification_silence_title">Silent</string>
@@ -2355,8 +2364,8 @@
<string name="system_multitasking_lhs">Use split screen with app on the left</string>
<!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
<string name="system_multitasking_full_screen">Use full screen</string>
- <!-- User visible title for the keyboard shortcut that switches to desktop view [CHAR LIMIT=70] -->
- <string name="system_multitasking_desktop_view">Use desktop view</string>
+ <!-- User visible title for the keyboard shortcut that switches to desktop windowing [CHAR LIMIT=70] -->
+ <string name="system_multitasking_desktop_view">Use desktop windowing</string>
<!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] -->
<string name="system_multitasking_splitscreen_focus_rhs">Switch to app on right or below while using split screen</string>
<!-- User visible title for the keyboard shortcut that switches to app on left or above while using split screen [CHAR LIMIT=70] -->
@@ -2599,6 +2608,9 @@
<!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+ <!-- Accessibility description indicating the currently selected tile is already added [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_tile_already_added">Tile already added</string>
+
<!-- Accessibility announcement after a tile has been added [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_added">Tile added</string>
@@ -3176,8 +3188,6 @@
<string name="controls_media_active_session">The current media session cannot be hidden.</string>
<!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
<string name="controls_media_dismiss_button">Hide</string>
- <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
- <string name="controls_media_resume">Resume</string>
<!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
<string name="controls_media_settings_button">Settings</string>
<!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4431ddadc8de..7895ff7b90f6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -895,57 +895,6 @@
<item name="android:textColor">@android:color/system_on_primary_dark</item>
</style>
- <style name="MediaPlayer.Recommendation"/>
-
- <style name="MediaPlayer.Recommendation.Header">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
- <item name="android:layout_marginStart">@dimen/qs_media_padding</item>
- <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
- <item name="android:singleLine">true</item>
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.AlbumContainer">
- <item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
- <item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
- <item name="android:background">@drawable/qs_media_light_source</item>
- <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
- <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
- <item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
- <item name="android:minWidth">@dimen/qs_media_rec_album_width</item>
- <item name="android:minHeight">@dimen/qs_media_rec_album_height_collapsed</item>
- <item name="android:background">@drawable/qs_media_light_source</item>
- <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Album">
- <item name="android:backgroundTint">@color/media_player_album_bg</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Text">
- <item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:maxLines">1</item>
- <item name="android:ellipsize">end</item>
- <item name="android:textSize">14sp</item>
- <item name="android:gravity">start</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Text.Title">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Text.Subtitle">
- <item name="android:textColor">?android:attr/textColorSecondary</item>
- </style>
-
-
<!-- Used to style charging animation AVD animation -->
<style name="ChargingAnim" />
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index faf06f3d39f0..bcd49b91d894 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -85,6 +85,16 @@
<item>On</item>
</string-array>
+ <!-- State names for dnd (Do not disturb) mode tile: unavailable, off, on.
+ This subtitle is shown when the tile is in that particular state but does not set its own
+ subtitle, so some of these may never appear on screen. They should still be translated as
+ if they could appear. [CHAR LIMIT=32] -->
+ <string-array name="tile_states_modes_dnd">
+ <item>Unavailable</item>
+ <item>Off</item>
+ <item>On</item>
+ </string-array>
+
<!-- State names for flashlight tile: unavailable, off, on.
This subtitle is shown when the tile is in that particular state but does not set its own
subtitle, so some of these may never appear on screen. They should still be translated as
diff --git a/packages/SystemUI/res/xml/media_recommendations_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
deleted file mode 100644
index d3be3c7de5ad..000000000000
--- a/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_collapsed"
- />
-
- <Constraint
- android:id="@+id/media_rec_title"
- style="@style/MediaPlayer.Recommendation.Header"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
-
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
deleted file mode 100644
index 88c70552e9e8..000000000000
--- a/packages/SystemUI/res/xml/media_recommendations_expanded.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded"
- />
-
- <Constraint
- android:id="@+id/media_rec_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_media_padding"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:singleLine="true"
- android:textSize="14sp"
- android:textColor="@color/notification_primary_text_color"/>
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_expanded"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
-
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_expanded"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_expanded"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-
-</ConstraintSet>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index ae3a76e2d2ca..8d7cab41ea20 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -51,6 +51,7 @@ android_library {
":wm_shell-shared-aidls",
],
static_libs: [
+ "com.android.systemui.dagger-api",
"BiometricsSharedLib",
"PlatformAnimationLib",
"PluginCoreLib",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
index a2b6e2cf9ae3..f276a82baaef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
@@ -41,7 +41,7 @@ class CombinedCondition
constructor(
private val scope: CoroutineScope,
private val conditions: Collection<Condition>,
- @Evaluator.ConditionOperand private val operand: Int
+ @Evaluator.ConditionOperand private val operand: Int,
) : Condition(scope, null, false) {
private var job: Job? = null
@@ -54,7 +54,7 @@ constructor(
lazilyEvaluate(
conditions = groupedConditions.getOrDefault(true, emptyList()),
- filterUnknown = true
+ filterUnknown = true,
)
.distinctUntilChanged()
.flatMapLatest { overriddenValue ->
@@ -62,7 +62,7 @@ constructor(
if (overriddenValue == null) {
lazilyEvaluate(
conditions = groupedConditions.getOrDefault(false, emptyList()),
- filterUnknown = false
+ filterUnknown = false,
)
} else {
flowOf(overriddenValue)
@@ -188,7 +188,6 @@ constructor(
return startStrategy
}
- override fun getStartStrategy(): Int {
- return _startStrategy
- }
+ override val startStrategy: Int
+ get() = _startStrategy
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
deleted file mode 100644
index 670feeba6e8a..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
+++ /dev/null
@@ -1,306 +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.shared.condition;
-
-import android.util.Log;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleEventObserver;
-import androidx.lifecycle.LifecycleOwner;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
- * its callbacks.
- */
-public abstract class Condition {
- private final String mTag = getClass().getSimpleName();
-
- private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
- private final boolean mOverriding;
- private final CoroutineScope mScope;
- private Boolean mIsConditionMet;
- private boolean mStarted = false;
-
- /**
- * By default, conditions have an initial value of false and are not overriding.
- */
- public Condition(CoroutineScope scope) {
- this(scope, false, false);
- }
-
- /**
- * Constructor for specifying initial state and overriding condition attribute.
- *
- * @param initialConditionMet Initial state of the condition.
- * @param overriding Whether this condition overrides others.
- */
- protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) {
- mIsConditionMet = initialConditionMet;
- mOverriding = overriding;
- mScope = scope;
- }
-
- /**
- * Starts monitoring the condition.
- */
- protected abstract void start();
-
- /**
- * Stops monitoring the condition.
- */
- protected abstract void stop();
-
- /**
- * Condition should be started as soon as there is an active subscription.
- */
- public static final int START_EAGERLY = 0;
- /**
- * Condition should be started lazily only if needed. But once started, it will not be cancelled
- * unless there are no more active subscriptions.
- */
- public static final int START_LAZILY = 1;
- /**
- * Condition should be started lazily only if needed, and can be stopped when not needed. This
- * should be used for conditions which are expensive to keep running.
- */
- public static final int START_WHEN_NEEDED = 2;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({START_EAGERLY, START_LAZILY, START_WHEN_NEEDED})
- @interface StartStrategy {
- }
-
- @StartStrategy
- protected abstract int getStartStrategy();
-
- /**
- * Returns whether the current condition overrides
- */
- public boolean isOverridingCondition() {
- return mOverriding;
- }
-
- /**
- * Registers a callback to receive updates once started. This should be called before
- * {@link #start()}. Also triggers the callback immediately if already started.
- */
- public void addCallback(@NonNull Callback callback) {
- if (shouldLog()) Log.d(mTag, "adding callback");
- mCallbacks.add(new WeakReference<>(callback));
-
- if (mStarted) {
- callback.onConditionChanged(this);
- return;
- }
-
- start();
- mStarted = true;
- }
-
- /**
- * Removes the provided callback from further receiving updates.
- */
- public void removeCallback(@NonNull Callback callback) {
- if (shouldLog()) Log.d(mTag, "removing callback");
- final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
- while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
- if (cb == null || cb == callback) {
- iterator.remove();
- }
- }
-
- if (!mCallbacks.isEmpty() || !mStarted) {
- return;
- }
-
- stop();
- mStarted = false;
- }
-
- /**
- * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state
- * and {@link #removeCallback(Callback)} when not resumed automatically.
- */
- public Callback observe(LifecycleOwner owner, Callback listener) {
- return observe(owner.getLifecycle(), listener);
- }
-
- /**
- * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state
- * and {@link #removeCallback(Condition.Callback)} when not resumed automatically.
- */
- public Callback observe(Lifecycle lifecycle, Callback listener) {
- lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> {
- if (event == Lifecycle.Event.ON_RESUME) {
- addCallback(listener);
- } else if (event == Lifecycle.Event.ON_PAUSE) {
- removeCallback(listener);
- }
- });
- return listener;
- }
-
- /**
- * Updates the value for whether the condition has been fulfilled, and sends an update if the
- * value changes and any callback is registered.
- *
- * @param isConditionMet True if the condition has been fulfilled. False otherwise.
- */
- protected void updateCondition(boolean isConditionMet) {
- if (mIsConditionMet != null && mIsConditionMet == isConditionMet) {
- return;
- }
-
- if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet);
- mIsConditionMet = isConditionMet;
- sendUpdate();
- }
-
- /**
- * Clears the set condition value. This is purposefully separate from
- * {@link #updateCondition(boolean)} to avoid confusion around {@code null} values.
- */
- protected void clearCondition() {
- if (mIsConditionMet == null) {
- return;
- }
-
- if (shouldLog()) Log.d(mTag, "clearing condition");
-
- mIsConditionMet = null;
- sendUpdate();
- }
-
- private void sendUpdate() {
- final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
- while (iterator.hasNext()) {
- final Callback cb = iterator.next().get();
- if (cb == null) {
- iterator.remove();
- } else {
- cb.onConditionChanged(this);
- }
- }
- }
-
- /**
- * Returns whether the condition is set. This method should be consulted to understand the
- * value of {@link #isConditionMet()}.
- *
- * @return {@code true} if value is present, {@code false} otherwise.
- */
- public boolean isConditionSet() {
- return mIsConditionMet != null;
- }
-
- /**
- * Returns whether the condition has been met. Note that this method will return {@code false}
- * if the condition is not set as well.
- */
- public boolean isConditionMet() {
- return Boolean.TRUE.equals(mIsConditionMet);
- }
-
- protected final boolean shouldLog() {
- return Log.isLoggable(mTag, Log.DEBUG);
- }
-
- protected final String getTag() {
- if (isOverridingCondition()) {
- return mTag + "[OVRD]";
- }
-
- return mTag;
- }
-
- /**
- * Returns the state of the condition.
- * - "Invalid", condition hasn't been set / not monitored
- * - "True", condition has been met
- * - "False", condition has not been met
- */
- protected final String getState() {
- if (!isConditionSet()) {
- return "Invalid";
- }
- return isConditionMet() ? "True" : "False";
- }
-
- /**
- * Creates a new condition which will only be true when both this condition and all the provided
- * conditions are true.
- */
- public Condition and(@NonNull Collection<Condition> others) {
- final List<Condition> conditions = new ArrayList<>();
- conditions.add(this);
- conditions.addAll(others);
- return new CombinedCondition(mScope, conditions, Evaluator.OP_AND);
- }
-
- /**
- * Creates a new condition which will only be true when both this condition and the provided
- * condition is true.
- */
- public Condition and(@NonNull Condition... others) {
- return and(Arrays.asList(others));
- }
-
- /**
- * Creates a new condition which will only be true when either this condition or any of the
- * provided conditions are true.
- */
- public Condition or(@NonNull Collection<Condition> others) {
- final List<Condition> conditions = new ArrayList<>();
- conditions.add(this);
- conditions.addAll(others);
- return new CombinedCondition(mScope, conditions, Evaluator.OP_OR);
- }
-
- /**
- * Creates a new condition which will only be true when either this condition or the provided
- * condition is true.
- */
- public Condition or(@NonNull Condition... others) {
- return or(Arrays.asList(others));
- }
-
- /**
- * Callback that receives updates about whether the condition has been fulfilled.
- */
- public interface Callback {
- /**
- * Called when the fulfillment of the condition changes.
- *
- * @param condition The condition in question.
- */
- void onConditionChanged(Condition condition);
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt
new file mode 100644
index 000000000000..69236b48ed51
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.condition
+
+import android.util.Log
+import androidx.annotation.IntDef
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import java.lang.ref.WeakReference
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Base class for a condition that needs to be fulfilled in order for [Monitor] to inform its
+ * callbacks.
+ */
+abstract class Condition
+/**
+ * Constructor for specifying initial state and overriding condition attribute.
+ *
+ * @param initialConditionMet Initial state of the condition.
+ * @param overriding Whether this condition overrides others.
+ */
+@JvmOverloads
+protected constructor(
+ private val _scope: CoroutineScope,
+ private var _isConditionMet: Boolean? = false,
+ /** Returns whether the current condition overrides */
+ val isOverridingCondition: Boolean = false,
+) {
+ private val mTag: String = javaClass.simpleName
+
+ private val _callbacks = mutableListOf<WeakReference<Callback>>()
+ private var _started = false
+
+ /** Starts monitoring the condition. */
+ protected abstract fun start()
+
+ /** Stops monitoring the condition. */
+ protected abstract fun stop()
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(START_EAGERLY, START_LAZILY, START_WHEN_NEEDED)
+ annotation class StartStrategy
+
+ @get:StartStrategy abstract val startStrategy: Int
+
+ /**
+ * Registers a callback to receive updates once started. This should be called before [.start].
+ * Also triggers the callback immediately if already started.
+ */
+ fun addCallback(callback: Callback) {
+ if (shouldLog()) Log.d(mTag, "adding callback")
+ _callbacks.add(WeakReference(callback))
+
+ if (_started) {
+ callback.onConditionChanged(this)
+ return
+ }
+
+ start()
+ _started = true
+ }
+
+ /** Removes the provided callback from further receiving updates. */
+ fun removeCallback(callback: Callback) {
+ if (shouldLog()) Log.d(mTag, "removing callback")
+ val iterator = _callbacks.iterator()
+ while (iterator.hasNext()) {
+ val cb = iterator.next().get()
+ if (cb == null || cb === callback) {
+ iterator.remove()
+ }
+ }
+
+ if (_callbacks.isNotEmpty() || !_started) {
+ return
+ }
+
+ stop()
+ _started = false
+ }
+
+ /**
+ * Wrapper to [.addCallback] when a lifecycle is in the resumed state and [.removeCallback] when
+ * not resumed automatically.
+ */
+ fun observe(owner: LifecycleOwner, listener: Callback): Callback {
+ return observe(owner.lifecycle, listener)
+ }
+
+ /**
+ * Wrapper to [.addCallback] when a lifecycle is in the resumed state and [.removeCallback] when
+ * not resumed automatically.
+ */
+ fun observe(lifecycle: Lifecycle, listener: Callback): Callback {
+ lifecycle.addObserver(
+ LifecycleEventObserver { lifecycleOwner: LifecycleOwner?, event: Lifecycle.Event ->
+ if (event == Lifecycle.Event.ON_RESUME) {
+ addCallback(listener)
+ } else if (event == Lifecycle.Event.ON_PAUSE) {
+ removeCallback(listener)
+ }
+ }
+ )
+ return listener
+ }
+
+ /**
+ * Updates the value for whether the condition has been fulfilled, and sends an update if the
+ * value changes and any callback is registered.
+ *
+ * @param isConditionMet True if the condition has been fulfilled. False otherwise.
+ */
+ protected fun updateCondition(isConditionMet: Boolean) {
+ if (_isConditionMet != null && _isConditionMet == isConditionMet) {
+ return
+ }
+
+ if (shouldLog()) Log.d(mTag, "updating condition to $isConditionMet")
+ _isConditionMet = isConditionMet
+ sendUpdate()
+ }
+
+ /**
+ * Clears the set condition value. This is purposefully separate from [.updateCondition] to
+ * avoid confusion around `null` values.
+ */
+ fun clearCondition() {
+ if (_isConditionMet == null) {
+ return
+ }
+
+ if (shouldLog()) Log.d(mTag, "clearing condition")
+
+ _isConditionMet = null
+ sendUpdate()
+ }
+
+ private fun sendUpdate() {
+ val iterator = _callbacks.iterator()
+ while (iterator.hasNext()) {
+ val cb = iterator.next().get()
+ if (cb == null) {
+ iterator.remove()
+ } else {
+ cb.onConditionChanged(this)
+ }
+ }
+ }
+
+ val isConditionSet: Boolean
+ /**
+ * Returns whether the condition is set. This method should be consulted to understand the
+ * value of [.isConditionMet].
+ *
+ * @return `true` if value is present, `false` otherwise.
+ */
+ get() = _isConditionMet != null
+
+ val isConditionMet: Boolean
+ /**
+ * Returns whether the condition has been met. Note that this method will return `false` if
+ * the condition is not set as well.
+ */
+ get() = true == _isConditionMet
+
+ protected fun shouldLog(): Boolean {
+ return Log.isLoggable(mTag, Log.DEBUG)
+ }
+
+ val tag: String
+ get() {
+ if (isOverridingCondition) {
+ return "$mTag[OVRD]"
+ }
+
+ return mTag
+ }
+
+ val state: String
+ /**
+ * Returns the state of the condition.
+ * - "Invalid", condition hasn't been set / not monitored
+ * - "True", condition has been met
+ * - "False", condition has not been met
+ */
+ get() {
+ if (!isConditionSet) {
+ return "Invalid"
+ }
+ return if (isConditionMet) "True" else "False"
+ }
+
+ /**
+ * Creates a new condition which will only be true when both this condition and all the provided
+ * conditions are true.
+ */
+ fun and(others: Collection<Condition>): Condition {
+ val conditions: List<Condition> = listOf(this, *others.toTypedArray())
+ return CombinedCondition(_scope, conditions, Evaluator.OP_AND)
+ }
+
+ /**
+ * Creates a new condition which will only be true when both this condition and the provided
+ * condition is true.
+ */
+ fun and(vararg others: Condition): Condition {
+ return and(listOf(*others))
+ }
+
+ /**
+ * Creates a new condition which will only be true when either this condition or any of the
+ * provided conditions are true.
+ */
+ fun or(others: Collection<Condition>): Condition {
+ val conditions: MutableList<Condition> = ArrayList()
+ conditions.add(this)
+ conditions.addAll(others)
+ return CombinedCondition(_scope, conditions, Evaluator.OP_OR)
+ }
+
+ /**
+ * Creates a new condition which will only be true when either this condition or the provided
+ * condition is true.
+ */
+ fun or(vararg others: Condition): Condition {
+ return or(listOf(*others))
+ }
+
+ /** Callback that receives updates about whether the condition has been fulfilled. */
+ fun interface Callback {
+ /**
+ * Called when the fulfillment of the condition changes.
+ *
+ * @param condition The condition in question.
+ */
+ fun onConditionChanged(condition: Condition)
+ }
+
+ companion object {
+ /** Condition should be started as soon as there is an active subscription. */
+ const val START_EAGERLY: Int = 0
+
+ /**
+ * Condition should be started lazily only if needed. But once started, it will not be
+ * cancelled unless there are no more active subscriptions.
+ */
+ const val START_LAZILY: Int = 1
+
+ /**
+ * Condition should be started lazily only if needed, and can be stopped when not needed.
+ * This should be used for conditions which are expensive to keep running.
+ */
+ const val START_WHEN_NEEDED: Int = 2
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
index 84edc3577007..0f535cdfa5cc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
@@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
fun Flow<Boolean>.toCondition(
scope: CoroutineScope,
@StartStrategy strategy: Int,
- initialValue: Boolean? = null
+ initialValue: Boolean? = null,
): Condition {
return object : Condition(scope, initialValue, false) {
var job: Job? = null
@@ -28,7 +28,8 @@ fun Flow<Boolean>.toCondition(
job = null
}
- override fun getStartStrategy() = strategy
+ override val startStrategy: Int
+ get() = strategy
}
}
@@ -36,13 +37,16 @@ fun Flow<Boolean>.toCondition(
fun Condition.toFlow(): Flow<Boolean?> {
return callbackFlow {
val callback =
- Condition.Callback { condition ->
- if (condition.isConditionSet) {
- trySend(condition.isConditionMet)
- } else {
- trySend(null)
+ object : Condition.Callback {
+ override fun onConditionChanged(condition: Condition) {
+ if (condition.isConditionSet) {
+ trySend(condition.isConditionMet)
+ } else {
+ trySend(null)
+ }
}
}
+
addCallback(callback)
callback.onConditionChanged(this@toFlow)
awaitClose { removeCallback(callback) }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index 10b930381c44..ade63b1dc9c9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -66,7 +66,7 @@ oneway interface ILauncherProxy {
/**
* Sent when some system ui state changes.
*/
- void onSystemUiStateChanged(long stateFlags) = 16;
+ void onSystemUiStateChanged(long stateFlags, int displayId) = 16;
/**
* Sent when suggested rotation button could be shown
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index dcbacec5b630..c880f057f44a 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -203,7 +203,9 @@ public class CarrierTextManager {
CarrierTextManagerLogger logger) {
mContext = context;
- mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+ boolean hasTelephony = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ mIsEmergencyCallCapable = telephonyManager.isVoiceCapable() && hasTelephony;
mShowAirplaneMode = showAirplaneMode;
mShowMissingSim = showMissingSim;
@@ -221,9 +223,7 @@ public class CarrierTextManager {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLogger = logger;
mBgExecutor.execute(() -> {
- boolean supported = mContext.getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
- if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+ if (hasTelephony && mNetworkSupported.compareAndSet(false, hasTelephony)) {
// This will set/remove the listeners appropriately. Note that it will never double
// add the listeners.
handleSetListening(mCarrierTextCallback);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 94342349e727..f2f177356fab 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,7 +21,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
-import android.graphics.RectF
import android.os.Trace
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -56,9 +55,11 @@ import com.android.systemui.log.core.Logger
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEventListener
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockTickRate
+import com.android.systemui.plugins.clocks.VRectF
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
@@ -148,7 +149,7 @@ constructor(
val clockStr = clock.toString()
loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
- clock.initialize(isDarkTheme(), dozeAmount.value, 0f, { onClockBoundsChanged.value = it })
+ clock.initialize(isDarkTheme(), dozeAmount.value, 0f, clockListener)
if (!regionSamplingEnabled) {
updateColors()
@@ -249,7 +250,7 @@ constructor(
private var largeClockOnSecondaryDisplay = false
val dozeAmount = MutableStateFlow(0f)
- val onClockBoundsChanged = MutableStateFlow<RectF?>(null)
+ val onClockBoundsChanged = MutableStateFlow<VRectF>(VRectF.ZERO)
private fun isDarkTheme(): Boolean {
val isLightTheme = TypedValue()
@@ -312,6 +313,13 @@ constructor(
private var zenData: ZenData? = null
private var alarmData: AlarmData? = null
+ private val clockListener =
+ object : ClockEventListener {
+ override fun onBoundsChanged(bounds: VRectF) {
+ onClockBoundsChanged.value = bounds
+ }
+ }
+
private val configListener =
object : ConfigurationController.ConfigurationListener {
override fun onThemeChanged() {
@@ -386,7 +394,7 @@ constructor(
@VisibleForTesting
internal fun listenForDnd(scope: CoroutineScope): Job {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
return scope.launch {
zenModeInteractor.dndMode.collect {
val zenMode =
@@ -564,10 +572,6 @@ constructor(
}
fun handleFidgetTap(x: Float, y: Float) {
- if (!com.android.systemui.Flags.clockFidgetAnimation()) {
- return
- }
-
clock?.run {
smallClock.animations.onFidgetTap(x, y)
largeClock.animations.onFidgetTap(x, y)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index ec97b8a96c1f..b8726101602c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.hardware.input.InputManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -219,6 +220,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
private final BouncerHapticPlayer mBouncerHapticPlayer;
private final UserActivityNotifier mUserActivityNotifier;
+ private final InputManager mInputManager;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -235,7 +237,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
UiEventLogger uiEventLogger,
KeyguardKeyboardInteractor keyguardKeyboardInteractor,
BouncerHapticPlayer bouncerHapticPlayer,
- UserActivityNotifier userActivityNotifier) {
+ UserActivityNotifier userActivityNotifier,
+ InputManager inputManager) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -254,6 +257,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
mBouncerHapticPlayer = bouncerHapticPlayer;
mUserActivityNotifier = userActivityNotifier;
+ mInputManager = inputManager;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -285,22 +289,23 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer,
- mUserActivityNotifier);
+ mUserActivityNotifier, mInputManager);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
- mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier);
+ mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier,
+ mInputManager);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
- mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier
- );
+ mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier,
+ mInputManager);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 0e9d8fec9717..ec9aedfc7551 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -18,12 +18,14 @@ package com.android.keyguard;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput;
import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
+import android.hardware.input.InputManager;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -43,11 +45,13 @@ import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
- extends KeyguardAbsKeyInputViewController<T> {
+ extends KeyguardAbsKeyInputViewController<T> implements InputManager.InputDeviceListener {
private final FalsingCollector mFalsingCollector;
private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
protected PasswordTextView mPasswordEntry;
+ private Boolean mShowAnimations;
+ private InputManager mInputManager;
private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -79,7 +83,8 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
SelectedUserInteractor selectedUserInteractor,
KeyguardKeyboardInteractor keyguardKeyboardInteractor,
BouncerHapticPlayer bouncerHapticPlayer,
- UserActivityNotifier userActivityNotifier) {
+ UserActivityNotifier userActivityNotifier,
+ InputManager inputManager) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor,
@@ -87,6 +92,51 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
mFalsingCollector = falsingCollector;
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+ mInputManager = inputManager;
+ mShowAnimations = null;
+ }
+
+ private void updateAnimations(Boolean showAnimations) {
+ if (!hideLastCharWithPhysicalInput()) return;
+
+ if (showAnimations == null) {
+ showAnimations = !mLockPatternUtils
+ .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
+ }
+ if (mShowAnimations != null && showAnimations.equals(mShowAnimations)) return;
+ mShowAnimations = showAnimations;
+
+ for (NumPadKey button : mView.getButtons()) {
+ button.setAnimationEnabled(mShowAnimations);
+ }
+ mPasswordEntry.setShowPassword(mShowAnimations);
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ if (!hideLastCharWithPhysicalInput()) return;
+
+ // If we were showing animations before maybe the new device is a keyboard.
+ if (mShowAnimations) {
+ updateAnimations(null);
+ }
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ if (!hideLastCharWithPhysicalInput()) return;
+
+ // If we were hiding animations because of a keyboard the keyboard may have been unplugged.
+ if (!mShowAnimations) {
+ updateAnimations(null);
+ }
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ if (!hideLastCharWithPhysicalInput()) return;
+
+ updateAnimations(null);
}
@Override
@@ -95,7 +145,13 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
boolean showAnimations = !mLockPatternUtils
.isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
- mPasswordEntry.setShowPassword(showAnimations);
+ if (hideLastCharWithPhysicalInput()) {
+ mInputManager.registerInputDeviceListener(this, null);
+ updateAnimations(showAnimations);
+ } else {
+ mPasswordEntry.setShowPassword(showAnimations);
+ }
+
for (NumPadKey button : mView.getButtons()) {
button.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -103,7 +159,9 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
}
return false;
});
- button.setAnimationEnabled(showAnimations);
+ if (!hideLastCharWithPhysicalInput()) {
+ button.setAnimationEnabled(showAnimations);
+ }
button.setBouncerHapticHelper(mBouncerHapticPlayer);
}
mPasswordEntry.setOnKeyListener(mOnKeyListener);
@@ -191,6 +249,10 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
protected void onViewDetached() {
super.onViewDetached();
+ if (hideLastCharWithPhysicalInput()) {
+ mInputManager.unregisterInputDeviceListener(this);
+ }
+
for (NumPadKey button : mView.getButtons()) {
button.setOnTouchListener(null);
button.setBouncerHapticHelper(null);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 9ae4cc6a4b4f..eefcab38ecd3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import android.hardware.input.InputManager;
import android.view.View;
import com.android.internal.logging.UiEvent;
@@ -63,11 +64,13 @@ public class KeyguardPinViewController
SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
KeyguardKeyboardInteractor keyguardKeyboardInteractor,
BouncerHapticPlayer bouncerHapticPlayer,
- UserActivityNotifier userActivityNotifier) {
+ UserActivityNotifier userActivityNotifier,
+ InputManager inputManager) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
+ keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager
+ );
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 24f77d77dbe1..a5bb62c04d00 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -29,6 +29,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.hardware.input.InputManager;
import android.telephony.PinResult;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -97,11 +98,13 @@ public class KeyguardSimPinViewController
SelectedUserInteractor selectedUserInteractor,
KeyguardKeyboardInteractor keyguardKeyboardInteractor,
BouncerHapticPlayer bouncerHapticPlayer,
- UserActivityNotifier userActivityNotifier) {
+ UserActivityNotifier userActivityNotifier,
+ InputManager inputManager) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
+ keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager
+ );
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index e17e8cc05f7e..adede3dc058d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -25,6 +25,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.hardware.input.InputManager;
import android.telephony.PinResult;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -95,11 +96,13 @@ public class KeyguardSimPukViewController
SelectedUserInteractor selectedUserInteractor,
KeyguardKeyboardInteractor keyguardKeyboardInteractor,
BouncerHapticPlayer bouncerHapticPlayer,
- UserActivityNotifier userActivityNotifier) {
+ UserActivityNotifier userActivityNotifier,
+ InputManager inputManager) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
- keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
+ keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager
+ );
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd09e392c883..60ec051315d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -124,7 +124,6 @@ import com.android.settingslib.Utils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -301,7 +300,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
private final Provider<SceneInteractor> mSceneInteractor;
private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
private final Provider<CommunalSceneInteractor> mCommunalSceneInteractor;
- private final KeyguardServiceShowLockscreenInteractor mKeyguardServiceShowLockscreenInteractor;
+ private final Provider<KeyguardServiceShowLockscreenInteractor>
+ mKeyguardServiceShowLockscreenInteractor;
private final AuthController mAuthController;
private final UiEventLogger mUiEventLogger;
private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -2219,7 +2219,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
Provider<JavaAdapter> javaAdapter,
Provider<SceneInteractor> sceneInteractor,
Provider<CommunalSceneInteractor> communalSceneInteractor,
- KeyguardServiceShowLockscreenInteractor keyguardServiceShowLockscreenInteractor) {
+ Provider<KeyguardServiceShowLockscreenInteractor>
+ keyguardServiceShowLockscreenInteractor) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2553,7 +2554,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
if (KeyguardWmStateRefactor.isEnabled()) {
mJavaAdapter.get().alwaysCollectFlow(
- mKeyguardServiceShowLockscreenInteractor.getShowNowEvents(),
+ mKeyguardServiceShowLockscreenInteractor.get().getShowNowEvents(),
this::onKeyguardServiceShowLockscreenNowEvents
);
}
@@ -2923,13 +2924,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt
private boolean isPrimaryBouncerShowingOrWillBeShowing(
ObservableTransitionState transitionState
) {
- SceneContainerFlag.assertInNewMode();
+ SceneContainerFlag.unsafeAssertInNewMode();
return isPrimaryBouncerFullyShown(transitionState)
|| transitionState.isTransitioning(null, Overlays.Bouncer);
}
private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) {
- SceneContainerFlag.assertInNewMode();
+ SceneContainerFlag.unsafeAssertInNewMode();
return transitionState.isIdle(Overlays.Bouncer);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 4a8e4ed3f6f1..f72087efc03e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -42,7 +43,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
diff --git a/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt b/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt
index 5e29ba91ce42..442d4abb23f7 100644
--- a/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt
+++ b/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt
@@ -19,23 +19,28 @@ package com.android.systemui
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.BuildSpec
import com.android.systemui.kairos.Events
import com.android.systemui.kairos.EventsLoop
import com.android.systemui.kairos.ExperimentalKairosApi
import com.android.systemui.kairos.Incremental
import com.android.systemui.kairos.IncrementalLoop
import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.RootKairosNetwork
import com.android.systemui.kairos.State
import com.android.systemui.kairos.StateLoop
+import com.android.systemui.kairos.TransactionScope
+import com.android.systemui.kairos.activateSpec
+import com.android.systemui.kairos.effect
import com.android.systemui.kairos.launchKairosNetwork
import com.android.systemui.kairos.launchScope
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.Multibinds
import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -176,21 +181,40 @@ private class KairosBuilderImpl @Inject constructor() : KairosBuilder {
@SysUISingleton
@ExperimentalKairosApi
class KairosCoreStartable
-@Inject
-constructor(
- @Application private val appScope: CoroutineScope,
- private val kairosNetwork: KairosNetwork,
+private constructor(
+ private val appScope: CoroutineScope,
private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>,
-) : CoreStartable {
+ private val unwrappedNetwork: RootKairosNetwork,
+) : CoreStartable, KairosNetwork by unwrappedNetwork {
+
+ @Inject
+ constructor(
+ @Application appScope: CoroutineScope,
+ activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>,
+ ) : this(appScope, activatables, appScope.launchKairosNetwork())
+
+ private val started = CompletableDeferred<Unit>()
+
override fun start() {
appScope.launch {
- kairosNetwork.activateSpec {
+ unwrappedNetwork.activateSpec {
for (activatable in activatables.get()) {
launchScope { activatable.run { activate() } }
}
+ effect { started.complete(Unit) }
}
}
}
+
+ override suspend fun activateSpec(spec: BuildSpec<*>) {
+ started.await()
+ unwrappedNetwork.activateSpec(spec)
+ }
+
+ override suspend fun <R> transact(block: TransactionScope.() -> R): R {
+ started.await()
+ return unwrappedNetwork.transact(block)
+ }
}
@Module
@@ -203,10 +227,5 @@ interface KairosCoreStartableModule {
@Multibinds fun kairosActivatables(): Set<@JvmSuppressWildcards KairosActivatable>
- companion object {
- @Provides
- @SysUISingleton
- fun provideKairosNetwork(@Application scope: CoroutineScope): KairosNetwork =
- scope.launchKairosNetwork()
- }
+ @Binds fun bindKairosNetwork(impl: KairosCoreStartable): KairosNetwork
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesChecker.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesChecker.java
index 2d1cd03aea4d..20290f7b5373 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesChecker.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesChecker.java
@@ -99,10 +99,7 @@ public class HearingDevicesChecker {
private boolean isExclusivelyManagedBluetoothDevice(
@NonNull CachedBluetoothDevice cachedDevice) {
- if (com.android.settingslib.flags.Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- return BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- cachedDevice.getDevice());
- }
- return false;
+ return BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ cachedDevice.getDevice());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index b730c931be8b..14b13d105482 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -19,6 +19,8 @@ package com.android.systemui.accessibility.hearingaid;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
+
import static java.util.Collections.emptyList;
import android.bluetooth.BluetoothHapClient;
@@ -61,7 +63,7 @@ import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.H
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory;
-import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.ConnectedHearingDeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
@@ -69,6 +71,7 @@ import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.shared.QSSettingsPackageRepository;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -109,6 +112,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private final HearingDevicesUiEventLogger mUiEventLogger;
private final boolean mShowPairNewDevice;
private final int mLaunchSourceId;
+ private final QSSettingsPackageRepository mQSSettingsPackageRepository;
private SystemUIDialog mDialog;
private HearingDevicesListAdapter mDeviceListAdapter;
@@ -117,6 +121,13 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private Spinner mPresetSpinner;
private HearingDevicesPresetsController mPresetController;
private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
+
+ private View mInputRoutingLayout;
+ private Spinner mInputRoutingSpinner;
+ private HearingDevicesInputRoutingController.Factory mInputRoutingControllerFactory;
+ private HearingDevicesInputRoutingController mInputRoutingController;
+ private HearingDevicesSpinnerAdapter mInputRoutingAdapter;
+
private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
new HearingDevicesPresetsController.PresetCallback() {
@Override
@@ -140,12 +151,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
- // TODO(b/331305850): setHearingAidInfo() for connected but not connect to profile
- // hearing device only called from
- // settings/bluetooth/DeviceListPreferenceFragment#handleLeScanResult, so we don't know
- // it is connected but not yet connect to profile hearing device in systemui.
- // Show all connected but not connect to profile bluetooth device for now.
- new ConnectedDeviceItemFactory(),
+ new ConnectedHearingDeviceItemFactory(),
new SavedHearingDeviceItemFactory()
);
@@ -169,7 +175,9 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
AudioManager audioManager,
- HearingDevicesUiEventLogger uiEventLogger) {
+ HearingDevicesUiEventLogger uiEventLogger,
+ QSSettingsPackageRepository qsSettingsPackageRepository,
+ HearingDevicesInputRoutingController.Factory inputRoutingControllerFactory) {
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -181,6 +189,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mProfileManager = localBluetoothManager.getProfileManager();
mUiEventLogger = uiEventLogger;
mLaunchSourceId = launchSourceId;
+ mQSSettingsPackageRepository = qsSettingsPackageRepository;
+ mInputRoutingControllerFactory = inputRoutingControllerFactory;
}
@Override
@@ -196,11 +206,11 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, mLaunchSourceId);
dismissDialogIfExists();
- Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS);
Bundle bundle = new Bundle();
bundle.putString(KEY_BLUETOOTH_ADDRESS, deviceItem.getCachedBluetoothDevice().getAddress());
- intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS)
+ .setPackage(mQSSettingsPackageRepository.getSettingsPackageName())
+ .putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
mDialogTransitionAnimator.createActivityTransitionController(view));
}
@@ -238,6 +248,12 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mPresetLayout.setVisibility(
mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
}
+ if (mInputRoutingController != null) {
+ mInputRoutingController.setDevice(device);
+ mInputRoutingController.isInputRoutingControlAvailable(
+ available -> mMainExecutor.execute(() -> mInputRoutingLayout.setVisibility(
+ available ? VISIBLE : GONE)));
+ }
if (mAmbientController != null) {
mAmbientController.loadDevice(device);
}
@@ -263,6 +279,20 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
dialog.setTitle(R.string.quick_settings_hearing_devices_dialog_title);
dialog.setView(LayoutInflater.from(dialog.getContext()).inflate(
R.layout.hearing_devices_tile_dialog, null));
+ dialog.setNegativeButton(
+ R.string.hearing_devices_settings_button,
+ (dialogInterface, which) -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SETTINGS_CLICK,
+ mLaunchSourceId);
+ final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS)
+ .putExtra(Intent.EXTRA_COMPONENT_NAME,
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
+ mDialogTransitionAnimator.createActivityTransitionController(
+ dialog));
+ },
+ /* dismissOnClick = */ true
+ );
dialog.setPositiveButton(
R.string.quick_settings_done,
/* onClick = */ null,
@@ -295,6 +325,11 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
setupDeviceListView(dialog, hearingDeviceItemList);
setupPairNewDeviceButton(dialog);
setupPresetSpinner(dialog, activeHearingDevice);
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()
+ && com.android.systemui.Flags
+ .hearingDevicesInputRoutingUiImprovement()) {
+ setupInputRoutingSpinner(dialog, activeHearingDevice);
+ }
if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
setupAmbientControls(activeHearingDevice);
}
@@ -368,6 +403,52 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mBgExecutor.execute(() -> mPresetController.registerHapCallback());
}
+ private void setupInputRoutingSpinner(SystemUIDialog dialog,
+ CachedBluetoothDevice activeHearingDevice) {
+ mInputRoutingController = mInputRoutingControllerFactory.create(dialog.getContext());
+ mInputRoutingController.setDevice(activeHearingDevice);
+
+ mInputRoutingSpinner = dialog.requireViewById(R.id.input_routing_spinner);
+ mInputRoutingAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
+ mInputRoutingAdapter.addAll(
+ HearingDevicesInputRoutingController.getInputRoutingOptions(dialog.getContext()));
+ mInputRoutingSpinner.setAdapter(mInputRoutingAdapter);
+ // Disable redundant Touch & Hold accessibility action for Switch Access
+ mInputRoutingSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(@NonNull View host,
+ @NonNull AccessibilityNodeInfo info) {
+ info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ }
+ });
+ // Should call setSelection(index, false) for the spinner before setOnItemSelectedListener()
+ // to avoid extra onItemSelected() get called when first register the listener.
+ final int initialPosition =
+ mInputRoutingController.getUserPreferredInputRoutingValue();
+ mInputRoutingSpinner.setSelection(initialPosition, false);
+ mInputRoutingAdapter.setSelected(initialPosition);
+ mInputRoutingSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mInputRoutingAdapter.setSelected(position);
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_INPUT_ROUTING_SELECT,
+ mLaunchSourceId);
+ mInputRoutingController.selectInputRouting(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing
+ }
+ });
+
+ mInputRoutingLayout = dialog.requireViewById(R.id.input_routing_layout);
+ mInputRoutingController.isInputRoutingControlAvailable(
+ available -> mMainExecutor.execute(() -> mInputRoutingLayout.setVisibility(
+ available ? VISIBLE : GONE)));
+ }
+
private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) {
final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId);
@@ -385,8 +466,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
pairButton.setOnClickListener(v -> {
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId);
dismissDialogIfExists();
- final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS)
+ .setPackage(mQSSettingsPackageRepository.getSettingsPackageName());
mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
mDialogTransitionAnimator.createActivityTransitionController(dialog));
});
@@ -507,8 +588,9 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
com.android.internal.R.color.materialColorOnPrimaryContainer));
}
text.setText(item.getToolName());
- Intent intent = item.getToolIntent();
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ Intent intent = item.getToolIntent()
+ .setPackage(mQSSettingsPackageRepository.getSettingsPackageName());
+
view.setOnClickListener(v -> {
final String name = intent.getComponent() != null
? intent.getComponent().flattenToString()
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt
new file mode 100644
index 000000000000..e8fa7ba2268b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesInputRoutingController.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.hearingaid
+
+import android.content.Context
+import android.media.AudioManager
+import android.util.Log
+import androidx.collection.ArraySet
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.HapClientProfile
+import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants
+import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * The controller of the hearing device input routing.
+ *
+ * <p> It manages and update the input routing according to the value.
+ */
+open class HearingDevicesInputRoutingController
+@AssistedInject
+constructor(
+ @Assisted context: Context,
+ private val audioManager: AudioManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ private val audioRoutingHelper = HearingAidAudioRoutingHelper(context)
+ private var cachedDevice: CachedBluetoothDevice? = null
+ private val bgCoroutineScope = CoroutineScope(backgroundDispatcher)
+
+ /** Factory to create a [HearingDevicesInputRoutingController] instance. */
+ @AssistedFactory
+ interface Factory {
+ fun create(context: Context): HearingDevicesInputRoutingController
+ }
+
+ /** Possible input routing UI. Need to align with [getInputRoutingOptions] */
+ enum class InputRoutingValue {
+ HEARING_DEVICE,
+ BUILTIN_MIC,
+ }
+
+ companion object {
+ private const val TAG = "HearingDevicesInputRoutingController"
+
+ /** Gets input routing options as strings. */
+ @JvmStatic
+ fun getInputRoutingOptions(context: Context): Array<String> {
+ return context.resources.getStringArray(R.array.hearing_device_input_routing_options)
+ }
+ }
+
+ fun interface InputRoutingControlAvailableCallback {
+ fun onResult(available: Boolean)
+ }
+
+ /**
+ * Sets the device for this controller to control the input routing.
+ *
+ * @param device the [CachedBluetoothDevice] set to the controller
+ */
+ fun setDevice(device: CachedBluetoothDevice?) {
+ this@HearingDevicesInputRoutingController.cachedDevice = device
+ }
+
+ fun isInputRoutingControlAvailable(callback: InputRoutingControlAvailableCallback) {
+ bgCoroutineScope.launch {
+ val result = isInputRoutingControlAvailableInternal()
+ callback.onResult(result)
+ }
+ }
+
+ /**
+ * Checks if input routing control is available for the currently set device.
+ *
+ * @return `true` if input routing control is available.
+ */
+ private suspend fun isInputRoutingControlAvailableInternal(): Boolean {
+ val device = cachedDevice ?: return false
+
+ val memberDevices = device.memberDevice
+
+ val inputInfos =
+ withContext(backgroundDispatcher) {
+ audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
+ }
+ val supportedInputDeviceAddresses = ArraySet<String>()
+ supportedInputDeviceAddresses.add(device.address)
+ if (memberDevices.isNotEmpty()) {
+ memberDevices.forEach { supportedInputDeviceAddresses.add(it.address) }
+ }
+
+ val isValidInputDevice =
+ inputInfos.any { supportedInputDeviceAddresses.contains(it.address) }
+ // Not support ASHA hearing device for input routing feature
+ val isHapHearingDevice = device.profiles.any { profile -> profile is HapClientProfile }
+
+ if (isHapHearingDevice && !isValidInputDevice) {
+ Log.d(TAG, "Not supported input type hearing device.")
+ }
+ return isHapHearingDevice && isValidInputDevice
+ }
+
+ /** Gets the user's preferred [InputRoutingValue]. */
+ fun getUserPreferredInputRoutingValue(): Int {
+ val device = cachedDevice ?: return InputRoutingValue.HEARING_DEVICE.ordinal
+
+ return if (device.device.isMicrophonePreferredForCalls) {
+ InputRoutingValue.HEARING_DEVICE.ordinal
+ } else {
+ InputRoutingValue.BUILTIN_MIC.ordinal
+ }
+ }
+
+ /**
+ * Sets the input routing to [android.bluetooth.BluetoothDevice.setMicrophonePreferredForCalls]
+ * based on the input routing index.
+ *
+ * @param inputRoutingIndex The desired input routing index.
+ */
+ fun selectInputRouting(inputRoutingIndex: Int) {
+ val device = cachedDevice ?: return
+
+ val useBuiltinMic = (inputRoutingIndex == InputRoutingValue.BUILTIN_MIC.ordinal)
+ val status =
+ audioRoutingHelper.setPreferredInputDeviceForCalls(
+ device,
+ if (useBuiltinMic) HearingAidAudioRoutingConstants.RoutingValue.BUILTIN_DEVICE
+ else HearingAidAudioRoutingConstants.RoutingValue.AUTO,
+ )
+ if (!status) {
+ Log.d(TAG, "Fail to configure setPreferredInputDeviceForCalls")
+ }
+ setMicrophonePreferredForCallsForDeviceSet(device, !useBuiltinMic)
+ }
+
+ private fun setMicrophonePreferredForCallsForDeviceSet(
+ device: CachedBluetoothDevice?,
+ enabled: Boolean,
+ ) {
+ device ?: return
+ device.device.isMicrophonePreferredForCalls = enabled
+ val memberDevices = device.memberDevice
+ if (memberDevices.isNotEmpty()) {
+ memberDevices.forEach { d -> d.device.isMicrophonePreferredForCalls = enabled }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
index fe1d5040c6f5..82ac10d85e70 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
@@ -39,7 +39,11 @@ enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnu
@UiEvent(doc = "Expand the ambient volume controls")
HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153),
@UiEvent(doc = "Collapse the ambient volume controls")
- HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154);
+ HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154),
+ @UiEvent(doc = "Select a input routing option from input routing spinner")
+ HEARING_DEVICES_INPUT_ROUTING_SELECT(2155),
+ @UiEvent(doc = "Click on the device settings to enter hearing devices page")
+ HEARING_DEVICES_SETTINGS_CLICK(2172);
override fun getId(): Int = this.id
}
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 0b578c65e915..113df2026e81 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -122,6 +122,10 @@ constructor(
return false
}
+ fun isBackCallbackRegistered(): Boolean {
+ return isCallbackRegistered
+ }
+
private fun registerBackCallback() {
if (isCallbackRegistered) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 4c8a8f1c13d7..b8e95ee1dbf0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -539,7 +539,8 @@ public class AuthContainerView extends LinearLayout
}
public void show(WindowManager wm) {
- wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
+ wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle(),
+ mPromptViewModel.getPromptKind().getValue().isCredential()));
}
private void forceExecuteAnimatedIn() {
@@ -738,7 +739,8 @@ public class AuthContainerView extends LinearLayout
}
@VisibleForTesting
- static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
+ static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title,
+ boolean isCredentialView) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_SECURE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
@@ -754,7 +756,7 @@ public class AuthContainerView extends LinearLayout
& ~WindowInsets.Type.systemBars());
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.setTitle("BiometricPrompt");
- lp.accessibilityTitle = title;
+ lp.accessibilityTitle = isCredentialView ? " " : title;
lp.dimAmount = BACKGROUND_DIM_AMOUNT;
lp.token = windowToken;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 88694ae6db51..dfe8eb28b2a6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -179,7 +179,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final PowerInteractor mPowerInteractor;
@NonNull private final CoroutineScope mScope;
@NonNull private final InputManager mInputManager;
- @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@NonNull private final SelectedUserInteractor mSelectedUserInteractor;
private final boolean mIgnoreRefreshRate;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -292,7 +291,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mActivityTransitionAnimator,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
- mUdfpsKeyguardAccessibilityDelegate,
mKeyguardTransitionInteractor,
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
@@ -691,7 +689,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull InputManager inputManager,
@NonNull DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
- @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
@NonNull SelectedUserInteractor selectedUserInteractor,
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
@@ -742,7 +739,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mPowerInteractor = powerInteractor;
mScope = scope;
mInputManager = inputManager;
- mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 702f23718ee8..bdf58275effa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -107,7 +107,6 @@ constructor(
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
- private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val transitionInteractor: KeyguardTransitionInteractor,
private val selectedUserInteractor: SelectedUserInteractor,
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
deleted file mode 100644
index 99da660d1fda..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.content.res.Resources
-import android.os.Bundle
-import android.view.View
-import android.view.accessibility.AccessibilityNodeInfo
-import com.android.systemui.res.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import javax.inject.Inject
-
-@SysUISingleton
-class UdfpsKeyguardAccessibilityDelegate
-@Inject
-constructor(
- @Main private val resources: Resources,
- private val keyguardViewManager: StatusBarKeyguardViewManager,
-) : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- val clickAction =
- AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
- resources.getString(R.string.accessibility_bouncer)
- )
- info.addAction(clickAction)
- }
-
- override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
- // when an a11y service is enabled, double tapping on the fingerprint sensor should
- // show the primary bouncer
- return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
- keyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
- true
- } else super.performAccessibilityAction(host, action, args)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 39f55803bb73..c4e1ccf6b62e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -31,7 +31,7 @@ import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toSensorStrength
import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index 230b30bc548e..cce33fdf16c1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -21,7 +21,7 @@ import android.util.Log
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 40313e3158aa..6484116233ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -21,7 +21,7 @@ import android.content.res.Configuration
import com.android.systemui.biometrics.data.repository.DisplayStateRepository
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.display.data.repository.DisplayRepository
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index e4c45402bb52..6842c90e957c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -43,6 +43,9 @@ object CredentialPasswordViewBinder {
// the header info never changes - do it early
val header = viewModel.header.first()
passwordField.setTextOperationUser(UserHandle.of(header.user.userIdForPasswordEntry))
+ viewModel.inputBoxContentDescription.firstOrNull()?.let { descriptionId ->
+ passwordField.contentDescription = view.context.getString(descriptionId)
+ }
viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags }
if (requestFocusForInput) {
passwordField.requestFocus()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 30b98a658821..ceb2b10ab517 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -99,11 +99,6 @@ constructor(
show()
} else if (showIndicatorForDeviceEntry) {
show()
- overlayView?.announceForAccessibility(
- applicationContext.resources.getString(
- R.string.accessibility_side_fingerprint_indicator_label
- )
- )
} else {
hide()
}
@@ -182,6 +177,11 @@ constructor(
overlayShowAnimator.start()
+ /**
+ * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback
+ * from speaking @string/accessibility_fingerprint_label twice when sensor location
+ * indicator is in focus
+ */
it.setAccessibilityDelegate(
object : View.AccessibilityDelegate() {
override fun dispatchPopulateAccessibilityEvent(
@@ -190,8 +190,7 @@ constructor(
): Boolean {
return if (
event.getEventType() ===
- android.view.accessibility.AccessibilityEvent
- .TYPE_WINDOW_STATE_CHANGED
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
) {
true
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 0c5c723a4b2f..37cfc3342b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -59,6 +59,17 @@ constructor(
}
}
+ /** Input box accessibility description for text based credential views */
+ val inputBoxContentDescription: Flow<Int?> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pin -> R.string.keyguard_accessibility_pin_area
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.keyguard_accessibility_password
+ else -> null
+ }
+ }
+
/** If stealth mode is active (hide user credential input). */
val stealthMode: Flow<Boolean> =
credentialInteractor.prompt.map {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
index 7f1cb5da474d..dea3c472a476 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
@@ -21,7 +21,7 @@ import android.util.Log
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
index 5a5a51e53d63..8d066bbaa915 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -419,6 +419,8 @@ constructor(
}
nameView.text = item.deviceName
summaryView.text = item.connectionSummary
+ // needed for marquee
+ summaryView.isSelected = true
actionIconView.setOnClickListener {
mutableDeviceItemClick.value =
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
index 7ecd27647238..c5349c855948 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
@@ -99,7 +99,7 @@ constructor(
*/
fun bindDetailsView(view: View) {
// If `QsDetailedView` is not enabled, it should show the dialog.
- QsDetailedView.assertInNewMode()
+ QsDetailedView.unsafeAssertInNewMode()
cancelJob()
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
index 5863a9385234..7d8752ef7222 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
@@ -21,18 +21,14 @@ import com.android.systemui.plugins.qs.TileDetailsViewModel
class BluetoothDetailsViewModel(
private val onSettingsClick: () -> Unit,
val detailsContentViewModel: BluetoothDetailsContentViewModel,
-) : TileDetailsViewModel() {
+) : TileDetailsViewModel {
override fun clickOnSettingsButton() {
onSettingsClick()
}
- override fun getTitle(): String {
- // TODO: b/378513956 Update the placeholder text
- return "Bluetooth"
- }
+ // TODO: b/378513956 Update the placeholder text
+ override val title = "Bluetooth"
- override fun getSubTitle(): String {
- // TODO: b/378513956 Update the placeholder text
- return "Tap to connect or disconnect a device"
- }
+ // TODO: b/378513956 Update the placeholder text
+ override val subTitle = "Tap to connect or disconnect a device"
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 55d4d3efbe27..9e0f10277197 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -22,7 +22,7 @@ import android.bluetooth.BluetoothAdapter.STATE_ON
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index bfbc27d3a086..208e498126c1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -21,7 +21,6 @@ import android.content.Context
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.flags.Flags
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
@@ -215,19 +214,15 @@ internal class AvailableHearingDeviceItemFactory : AvailableMediaDeviceItemFacto
}
}
-internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
+internal open class ConnectedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
- return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
- BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, isOngoingCall)
- } else {
+ return !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, isOngoingCall)
- }
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -243,6 +238,19 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
}
}
+internal class ConnectedHearingDeviceItemFactory : ConnectedDeviceItemFactory() {
+ override fun isFilterMatched(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ isOngoingCall: Boolean,
+ audioSharingAvailable: Boolean,
+ ): Boolean {
+ return cachedDevice.isHearingDevice &&
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+ cachedDevice.device.isConnected
+ }
+}
+
internal open class SavedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
@@ -250,13 +258,9 @@ internal open class SavedDeviceItemFactory : DeviceItemFactory() {
isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
- return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
- cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
- !cachedDevice.isConnected
- } else {
- cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
- }
+ return !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+ !cachedDevice.isConnected
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -279,18 +283,12 @@ internal class SavedHearingDeviceItemFactory : SavedDeviceItemFactory() {
isOngoingCall: Boolean,
audioSharingAvailable: Boolean,
): Boolean {
- return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice(),
- ) &&
- cachedDevice.isHearingAidDevice &&
- cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
- !cachedDevice.isConnected
- } else {
- cachedDevice.isHearingAidDevice &&
- cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
- !cachedDevice.isConnected
- }
+ return !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+ context,
+ cachedDevice.getDevice(),
+ ) &&
+ cachedDevice.isHearingDevice &&
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+ !cachedDevice.isConnected
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index b606c19b3503..e458b8092cda 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -24,7 +24,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
index b9e1c55fbade..89208364178d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
@@ -28,7 +28,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.bouncer.data.model.SimBouncerModel
import com.android.systemui.bouncer.data.model.SimPukInputModel
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
index e6d6293733d4..636b3ab66dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -24,7 +24,7 @@ import com.android.systemui.brightness.shared.model.BrightnessLog
import com.android.systemui.brightness.shared.model.LinearBrightness
import com.android.systemui.brightness.shared.model.formatBrightness
import com.android.systemui.brightness.shared.model.logDiffForTable
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index 79748a255ed0..6aeb35b3b158 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -19,6 +19,7 @@ package com.android.systemui.brightness.ui.compose
import android.content.Context
import android.view.MotionEvent
import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.VectorConverter
@@ -40,6 +41,7 @@ import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -165,20 +167,15 @@ fun BrightnessSlider(
val activeIconColor = colors.activeTickColor
val inactiveIconColor = colors.inactiveTickColor
- val trackIcon: DrawScope.(Offset, Color, Float) -> Unit =
- remember(painter) {
- { offset, color, alpha ->
- translate(offset.x + IconPadding.toPx(), offset.y) {
- with(painter) {
- draw(
- IconSize.toSize(),
- colorFilter = ColorFilter.tint(color),
- alpha = alpha,
- )
- }
+ val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember {
+ { offset, color, alpha ->
+ translate(offset.x + IconPadding.toPx(), offset.y) {
+ with(painter) {
+ draw(IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha)
}
}
}
+ }
Slider(
value = animatedValue,
@@ -328,7 +325,7 @@ private fun Modifier.sliderBackground(color: Color) = drawWithCache {
fun BrightnessSliderContainer(
viewModel: BrightnessSliderViewModel,
modifier: Modifier = Modifier,
- containerColor: Color = colorResource(R.color.shade_scrim_background_dark),
+ containerColors: ContainerColors,
) {
val gamma = viewModel.currentBrightness.value
if (gamma == BrightnessSliderViewModel.initialValue.value) { // Ignore initial negative value.
@@ -349,6 +346,16 @@ fun BrightnessSliderContainer(
DisposableEffect(Unit) { onDispose { viewModel.setIsDragging(false) } }
+ var dragging by remember { mutableStateOf(false) }
+
+ // Use dragging instead of viewModel.showMirror so the color starts changing as soon as the
+ // dragging state changes. If not, we may be waiting for the background to finish fading in
+ // when stopping dragging
+ val containerColor by
+ animateColorAsState(
+ if (dragging) containerColors.mirrorColor else containerColors.idleColor
+ )
+
Box(
modifier =
modifier
@@ -365,10 +372,12 @@ fun BrightnessSliderContainer(
onRestrictedClick = viewModel::showPolicyRestrictionDialog,
onDrag = {
viewModel.setIsDragging(true)
+ dragging = true
coroutineScope.launch { viewModel.onDrag(Drag.Dragging(GammaBrightness(it))) }
},
onStop = {
viewModel.setIsDragging(false)
+ dragging = false
coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) }
},
modifier =
@@ -397,6 +406,15 @@ fun BrightnessSliderContainer(
}
}
+data class ContainerColors(val idleColor: Color, val mirrorColor: Color) {
+ companion object {
+ fun singleColor(color: Color) = ContainerColors(color, color)
+
+ val defaultContainerColor: Color
+ @Composable @ReadOnlyComposable get() = colorResource(R.color.shade_panel_fallback)
+ }
+}
+
private object Dimensions {
val SliderBackgroundFrameSize = DpSize(10.dp, 6.dp)
val SliderBackgroundRoundedCorner = 24.dp
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 183a3cc26b95..724670d955dd 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.BroadcastRunning
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
index 7816a1487c01..dac5b7efaade 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
@@ -19,7 +19,7 @@ package com.android.systemui.camera.data.repository
import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.Sensors.CAMERA
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
new file mode 100644
index 000000000000..8cebe04d4e01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.text.TextUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ActionIntentCreator
+@Inject
+constructor(@Application private val applicationScope: CoroutineScope) : IntentCreator {
+ override fun getTextEditorIntent(context: Context?) =
+ Intent(context, EditTextActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ }
+
+ override fun getShareIntent(clipData: ClipData, context: Context?): Intent {
+ val shareIntent = Intent(Intent.ACTION_SEND)
+
+ // From the ACTION_SEND docs:
+ // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the
+ // MIME type of the data in EXTRA_STREAM"
+ val uri = clipData.getItemAt(0).uri
+ shareIntent.apply {
+ if (uri != null) {
+ // We don't use setData here because some apps interpret this as "to:".
+ setType(clipData.description.getMimeType(0))
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ setClipData(
+ ClipData(
+ ClipDescription("content", arrayOf(clipData.description.getMimeType(0))),
+ ClipData.Item(uri),
+ )
+ )
+ putExtra(Intent.EXTRA_STREAM, uri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ } else {
+ putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString())
+ setType("text/plain")
+ }
+ }
+
+ return Intent.createChooser(shareIntent, null)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ suspend fun getImageEditIntent(uri: Uri?, context: Context): Intent {
+ val editorPackage = context.getString(R.string.config_screenshotEditor)
+ return Intent(Intent.ACTION_EDIT).apply {
+ if (!TextUtils.isEmpty(editorPackage)) {
+ setComponent(ComponentName.unflattenFromString(editorPackage))
+ }
+ setDataAndType(uri, "image/*")
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD)
+ }
+ }
+
+ override fun getImageEditIntentAsync(
+ uri: Uri?,
+ context: Context,
+ outputConsumer: Consumer<Intent>,
+ ) {
+ applicationScope.launch { outputConsumer.accept(getImageEditIntent(uri, context)) }
+ }
+
+ override fun getRemoteCopyIntent(clipData: ClipData?, context: Context): Intent {
+ val remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage)
+ return Intent(REMOTE_COPY_ACTION).apply {
+ if (!TextUtils.isEmpty(remoteCopyPackage)) {
+ setComponent(ComponentName.unflattenFromString(remoteCopyPackage))
+ }
+
+ setClipData(clipData)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ }
+ }
+
+ companion object {
+ private const val EXTRA_EDIT_SOURCE: String = "edit_source"
+ private const val EDIT_SOURCE_CLIPBOARD: String = "clipboard"
+ private const val REMOTE_COPY_ACTION: String = "android.intent.action.REMOTE_COPY"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index ac747845267c..984d2478eb72 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -65,7 +65,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TimeoutHandler;
@@ -94,13 +93,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final ClipboardOverlayUtils mClipboardUtils;
- private final FeatureFlags mFeatureFlags;
private final Executor mBgExecutor;
private final ClipboardImageLoader mClipboardImageLoader;
private final ClipboardTransitionExecutor mTransitionExecutor;
private final ClipboardOverlayView mView;
private final ClipboardIndicationProvider mClipboardIndicationProvider;
+ private final IntentCreator mIntentCreator;
private Runnable mOnSessionCompleteListener;
private Runnable mOnRemoteCopyTapped;
@@ -189,13 +188,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender,
TimeoutHandler timeoutHandler,
- FeatureFlags featureFlags,
ClipboardOverlayUtils clipboardUtils,
@Background Executor bgExecutor,
ClipboardImageLoader clipboardImageLoader,
ClipboardTransitionExecutor transitionExecutor,
ClipboardIndicationProvider clipboardIndicationProvider,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ IntentCreator intentCreator) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mClipboardImageLoader = clipboardImageLoader;
@@ -203,6 +202,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mClipboardIndicationProvider = clipboardIndicationProvider;
mClipboardLogger = new ClipboardLogger(uiEventLogger);
+ mIntentCreator = intentCreator;
mView = clipboardOverlayView;
mWindow = clipboardOverlayWindow;
@@ -211,7 +211,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
hideImmediate();
});
- mFeatureFlags = featureFlags;
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
@@ -508,7 +507,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
}
private void maybeShowRemoteCopy(ClipData clipData) {
- Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
+ Intent remoteCopyIntent = mIntentCreator.getRemoteCopyIntent(clipData, mContext);
+
// Only show remote copy if it's available.
PackageManager packageManager = mContext.getPackageManager();
if (packageManager.resolveActivity(
@@ -558,19 +558,21 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private void editImage(Uri uri) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
- mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
- animateOut();
+ mIntentCreator.getImageEditIntentAsync(uri, mContext, intent -> {
+ mContext.startActivity(intent);
+ animateOut();
+ });
}
private void editText() {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
- mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
+ mContext.startActivity(mIntentCreator.getTextEditorIntent(mContext));
animateOut();
}
private void shareContent(ClipData clip) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
- mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
+ mContext.startActivity(mIntentCreator.getShareIntent(clip, mContext));
animateOut();
}
@@ -717,22 +719,22 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
public void onRemoteCopyButtonTapped() {
if (clipboardSharedTransitions()) {
finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
- IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
+ mIntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
}
}
@Override
public void onShareButtonTapped() {
if (clipboardSharedTransitions()) {
+ Intent shareIntent =
+ mIntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext);
switch (mClipboardModel.getType()) {
case TEXT:
case URI:
- finish(CLIPBOARD_OVERLAY_SHARE_TAPPED,
- IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ finish(CLIPBOARD_OVERLAY_SHARE_TAPPED, shareIntent);
break;
case IMAGE:
- finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
- IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+ finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED, shareIntent);
break;
}
}
@@ -744,11 +746,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
switch (mClipboardModel.getType()) {
case TEXT:
finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
- IntentCreator.getTextEditorIntent(mContext));
+ mIntentCreator.getTextEditorIntent(mContext));
break;
case IMAGE:
- finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED,
- IntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext));
+ mIntentCreator.getImageEditIntentAsync(mClipboardModel.getUri(), mContext,
+ intent -> {
+ finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, intent);
+ });
break;
default:
Log.w(TAG, "Got preview tapped callback for non-editable type "
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java
new file mode 100644
index 000000000000..e9a9cbf106fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+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 android.text.TextUtils;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.res.R;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+@SysUISingleton
+public class DefaultIntentCreator implements IntentCreator {
+ private static final String EXTRA_EDIT_SOURCE = "edit_source";
+ private static final String EDIT_SOURCE_CLIPBOARD = "clipboard";
+ private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+ @Inject
+ public DefaultIntentCreator() {}
+
+ public Intent getTextEditorIntent(Context context) {
+ Intent intent = new Intent(context, EditTextActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return intent;
+ }
+
+ public Intent getShareIntent(ClipData clipData, Context context) {
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+
+ // From the ACTION_SEND docs:
+ // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the
+ // MIME type of the data in EXTRA_STREAM"
+ Uri uri = clipData.getItemAt(0).getUri();
+ if (uri != null) {
+ // We don't use setData here because some apps interpret this as "to:".
+ shareIntent.setType(clipData.getDescription().getMimeType(0));
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ shareIntent.setClipData(new ClipData(
+ new ClipDescription(
+ "content", new String[]{clipData.getDescription().getMimeType(0)}),
+ new ClipData.Item(uri)));
+ shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
+ shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } else {
+ shareIntent.putExtra(
+ Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString());
+ shareIntent.setType("text/plain");
+ }
+ Intent chooserIntent = Intent.createChooser(shareIntent, null)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ return chooserIntent;
+ }
+
+ public void getImageEditIntentAsync(Uri uri, Context context, Consumer<Intent> outputConsumer) {
+ String editorPackage = context.getString(R.string.config_screenshotEditor);
+ Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ editIntent.setDataAndType(uri, "image/*");
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD);
+ outputConsumer.accept(editIntent);
+ }
+
+ public Intent getRemoteCopyIntent(ClipData clipData, Context context) {
+ Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
+
+ String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage);
+ if (!TextUtils.isEmpty(remoteCopyPackage)) {
+ nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage));
+ }
+
+ nearbyIntent.setClipData(clipData);
+ nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return nearbyIntent;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index a18b4c84b081..283596f9f309 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,79 +17,15 @@
package com.android.systemui.clipboardoverlay;
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 android.text.TextUtils;
-import com.android.systemui.res.R;
+import java.util.function.Consumer;
-class IntentCreator {
- private static final String EXTRA_EDIT_SOURCE = "edit_source";
- private static final String EDIT_SOURCE_CLIPBOARD = "clipboard";
- private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
-
- static Intent getTextEditorIntent(Context context) {
- Intent intent = new Intent(context, EditTextActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return intent;
- }
-
- static Intent getShareIntent(ClipData clipData, Context context) {
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
-
- // From the ACTION_SEND docs:
- // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the
- // MIME type of the data in EXTRA_STREAM"
- Uri uri = clipData.getItemAt(0).getUri();
- if (uri != null) {
- // We don't use setData here because some apps interpret this as "to:".
- shareIntent.setType(clipData.getDescription().getMimeType(0));
- // Include URI in ClipData also, so that grantPermission picks it up.
- shareIntent.setClipData(new ClipData(
- new ClipDescription(
- "content", new String[]{clipData.getDescription().getMimeType(0)}),
- new ClipData.Item(uri)));
- shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- } else {
- shareIntent.putExtra(
- Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString());
- shareIntent.setType("text/plain");
- }
- Intent chooserIntent = Intent.createChooser(shareIntent, null)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- return chooserIntent;
- }
-
- static Intent getImageEditIntent(Uri uri, Context context) {
- String editorPackage = context.getString(R.string.config_screenshotEditor);
- Intent editIntent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- editIntent.setDataAndType(uri, "image/*");
- editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD);
- return editIntent;
- }
-
- static Intent getRemoteCopyIntent(ClipData clipData, Context context) {
- Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
-
- String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage);
- if (!TextUtils.isEmpty(remoteCopyPackage)) {
- nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage));
- }
-
- nearbyIntent.setClipData(clipData);
- nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return nearbyIntent;
- }
+public interface IntentCreator {
+ Intent getTextEditorIntent(Context context);
+ Intent getShareIntent(ClipData clipData, Context context);
+ void getImageEditIntentAsync(Uri uri, Context context, Consumer<Intent> outputConsumer);
+ Intent getRemoteCopyIntent(ClipData clipData, Context context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index 6c10eea07ffc..c86a84b17efe 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.clipboardOverlayMultiuser;
import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.shared.Flags.usePreferredImageEditor;
import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -32,7 +33,10 @@ import android.view.WindowManager;
import com.android.app.viewcapture.ViewCapture;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
+import com.android.systemui.clipboardoverlay.ActionIntentCreator;
import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
+import com.android.systemui.clipboardoverlay.DefaultIntentCreator;
+import com.android.systemui.clipboardoverlay.IntentCreator;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -102,6 +106,17 @@ public interface ClipboardOverlayModule {
/* isViewCaptureEnabled= */ enableViewCaptureTracing());
}
+ @Provides
+ static IntentCreator provideIntentCreator(
+ Lazy<DefaultIntentCreator> defaultIntentCreator,
+ Lazy<ActionIntentCreator> actionIntentCreator) {
+ if (usePreferredImageEditor()) {
+ return actionIntentCreator.get();
+ } else {
+ return defaultIntentCreator.get();
+ }
+ }
+
@Qualifier
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
new file mode 100644
index 000000000000..9db7b50905f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.model.StateChange
+import com.android.systemui.model.SysUiState
+import javax.inject.Inject
+
+/** Handles [SysUiState] changes between displays. */
+@SysUISingleton
+class SysUIStateDisplaysInteractor
+@Inject
+constructor(
+ private val sysUIStateRepository: PerDisplayRepository<SysUiState>,
+ private val displayRepository: DisplayRepository,
+) {
+
+ /**
+ * Sets the flags on the given [targetDisplayId] based on the [stateChanges], while making sure
+ * that those flags are not set in any other display.
+ */
+ fun setFlagsExclusivelyToDisplay(targetDisplayId: Int, stateChanges: StateChange) {
+ if (SysUiState.DEBUG) {
+ Log.d(TAG, "Setting flags $stateChanges only for display $targetDisplayId")
+ }
+ displayRepository.displays.value
+ .mapNotNull { sysUIStateRepository[it.displayId] }
+ .apply {
+ // Let's first modify all states, without committing changes ...
+ forEach { displaySysUIState ->
+ if (displaySysUIState.displayId == targetDisplayId) {
+ stateChanges.applyTo(displaySysUIState)
+ } else {
+ stateChanges.clearFrom(displaySysUIState)
+ }
+ }
+ // ... And commit changes at the end
+ forEach { sysuiState -> sysuiState.commitUpdate() }
+ }
+ }
+
+ private companion object {
+ const val TAG = "SysUIStateInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
index d53a737480a3..72159252efec 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
@@ -19,11 +19,13 @@ package com.android.systemui.common.shared.model
import android.annotation.AttrRes
import android.annotation.ColorInt
import android.annotation.ColorRes
+import androidx.compose.runtime.Stable
/**
* Models a color that can be either a specific [Color.Loaded] value or a resolvable theme
* [Color.Attribute]
*/
+@Stable
sealed interface Color {
data class Loaded(@ColorInt val color: Int) : Color
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 d628aca7f9e8..b7d8ae6ce153 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
@@ -18,11 +18,13 @@ package com.android.systemui.common.shared.model
import android.annotation.StringRes
import android.content.Context
+import androidx.compose.runtime.Stable
/**
* Models a content description, that can either be already [loaded][ContentDescription.Loaded] or
* be a [reference][ContentDescription.Resource] to a resource.
*/
+@Stable
sealed class ContentDescription {
data class Loaded(val description: String?) : ContentDescription()
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index e6f02457d320..2adaec21867f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -18,11 +18,13 @@ package com.android.systemui.common.shared.model
import android.annotation.DrawableRes
import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Stable
/**
* Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference]
* [Icon.Resource] to a resource. In case of [Loaded], the resource ID [res] is optional.
*/
+@Stable
sealed class Icon {
abstract val contentDescription: ContentDescription?
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 13f6bba01135..07cc136e6bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -147,7 +147,7 @@ constructor(
*/
fun create(
context: Context,
- configurationController: ConfigurationController
+ configurationController: ConfigurationController,
): ConfigurationStateImpl
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt
index 42f1b738ec20..6c3535a42a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt
@@ -27,17 +27,17 @@ import android.view.ViewConfiguration
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.android.systemui.Flags.doubleTapToSleep
import com.android.systemui.log.TouchHandlingViewLogger
import com.android.systemui.shade.TouchLogger
-import kotlin.math.pow
-import kotlin.math.sqrt
import kotlinx.coroutines.DisposableHandle
/**
- * View designed to handle long-presses.
+ * View designed to handle long-presses and double taps.
*
- * The view will not handle any long pressed by default. To set it up, set up a listener and, when
- * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`.
+ * The view will not handle any gestures by default. To set it up, set up a listener and, when ready
+ * to start consuming gestures, set the gesture's enable function ([setLongPressHandlingEnabled],
+ * [setDoublePressHandlingEnabled]) to `true`.
*/
class TouchHandlingView(
context: Context,
@@ -62,6 +62,9 @@ class TouchHandlingView(
/** Notifies that the gesture was too short for a long press, it is actually a click. */
fun onSingleTapDetected(view: View, x: Int, y: Int) = Unit
+
+ /** Notifies that a double tap has been detected by the given view. */
+ fun onDoubleTapDetected(view: View) = Unit
}
var listener: Listener? = null
@@ -70,6 +73,7 @@ class TouchHandlingView(
private val interactionHandler: TouchHandlingViewInteractionHandler by lazy {
TouchHandlingViewInteractionHandler(
+ context = context,
postDelayed = { block, timeoutMs ->
val dispatchToken = Any()
@@ -84,6 +88,9 @@ class TouchHandlingView(
onSingleTapDetected = { x, y ->
listener?.onSingleTapDetected(this@TouchHandlingView, x = x, y = y)
},
+ onDoubleTapDetected = {
+ if (doubleTapToSleep()) listener?.onDoubleTapDetected(this@TouchHandlingView)
+ },
longPressDuration = longPressDuration,
allowedTouchSlop = allowedTouchSlop,
logger = logger,
@@ -100,13 +107,17 @@ class TouchHandlingView(
interactionHandler.isLongPressHandlingEnabled = isEnabled
}
+ fun setDoublePressHandlingEnabled(isEnabled: Boolean) {
+ interactionHandler.isDoubleTapHandlingEnabled = isEnabled
+ }
+
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
}
@SuppressLint("ClickableViewAccessibility")
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- return interactionHandler.onTouchEvent(event?.toModel())
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ return interactionHandler.onTouchEvent(event)
}
private fun setupAccessibilityDelegate() {
@@ -154,33 +165,3 @@ class TouchHandlingView(
}
}
}
-
-private fun MotionEvent.toModel(): TouchHandlingViewInteractionHandler.MotionEventModel {
- return when (actionMasked) {
- MotionEvent.ACTION_DOWN ->
- TouchHandlingViewInteractionHandler.MotionEventModel.Down(x = x.toInt(), y = y.toInt())
- MotionEvent.ACTION_MOVE ->
- TouchHandlingViewInteractionHandler.MotionEventModel.Move(
- distanceMoved = distanceMoved()
- )
- MotionEvent.ACTION_UP ->
- TouchHandlingViewInteractionHandler.MotionEventModel.Up(
- distanceMoved = distanceMoved(),
- gestureDuration = gestureDuration(),
- )
- MotionEvent.ACTION_CANCEL -> TouchHandlingViewInteractionHandler.MotionEventModel.Cancel
- else -> TouchHandlingViewInteractionHandler.MotionEventModel.Other
- }
-}
-
-private fun MotionEvent.distanceMoved(): Float {
- return if (historySize > 0) {
- sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
- } else {
- 0f
- }
-}
-
-private fun MotionEvent.gestureDuration(): Long {
- return eventTime - downTime
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt
index 5863fc644c8e..fe509d74edc0 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt
@@ -17,12 +17,20 @@
package com.android.systemui.common.ui.view
+import android.content.Context
import android.graphics.Point
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.ViewConfiguration
import com.android.systemui.log.TouchHandlingViewLogger
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlin.properties.Delegates
import kotlinx.coroutines.DisposableHandle
/** Encapsulates logic to handle complex touch interactions with a [TouchHandlingView]. */
class TouchHandlingViewInteractionHandler(
+ context: Context,
/**
* Callback to run the given [Runnable] with the given delay, returning a [DisposableHandle]
* allowing the delayed runnable to be canceled before it is run.
@@ -34,6 +42,8 @@ class TouchHandlingViewInteractionHandler(
private val onLongPressDetected: (x: Int, y: Int) -> Unit,
/** Callback reporting the a single tap gesture was detected at the given coordinates. */
private val onSingleTapDetected: (x: Int, y: Int) -> Unit,
+ /** Callback reporting that a double tap gesture was detected. */
+ private val onDoubleTapDetected: () -> Unit,
/** Time for the touch to be considered a long-press in ms */
var longPressDuration: () -> Long,
/**
@@ -58,48 +68,98 @@ class TouchHandlingViewInteractionHandler(
}
var isLongPressHandlingEnabled: Boolean = false
+ var isDoubleTapHandlingEnabled: Boolean = false
var scheduledLongPressHandle: DisposableHandle? = null
+ private var doubleTapAwaitingUp: Boolean = false
+ private var lastDoubleTapDownEventTime: Long? = null
+
/** Record coordinate for last DOWN event for single tap */
val lastEventDownCoordinate = Point(-1, -1)
- fun onTouchEvent(event: MotionEventModel?): Boolean {
- if (!isLongPressHandlingEnabled) {
- return false
- }
- return when (event) {
- is MotionEventModel.Down -> {
- scheduleLongPress(event.x, event.y)
- lastEventDownCoordinate.x = event.x
- lastEventDownCoordinate.y = event.y
- true
+ private val gestureDetector =
+ GestureDetector(
+ context,
+ object : GestureDetector.SimpleOnGestureListener() {
+ override fun onDoubleTap(event: MotionEvent): Boolean {
+ if (isDoubleTapHandlingEnabled) {
+ doubleTapAwaitingUp = true
+ lastDoubleTapDownEventTime = event.eventTime
+ return true
+ }
+ return false
+ }
+ },
+ )
+
+ fun onTouchEvent(event: MotionEvent): Boolean {
+ if (isDoubleTapHandlingEnabled) {
+ gestureDetector.onTouchEvent(event)
+ if (event.actionMasked == MotionEvent.ACTION_UP && doubleTapAwaitingUp) {
+ lastDoubleTapDownEventTime?.let { time ->
+ if (
+ event.eventTime - time < ViewConfiguration.getDoubleTapTimeout()
+ ) {
+ cancelScheduledLongPress()
+ onDoubleTapDetected()
+ }
+ }
+ doubleTapAwaitingUp = false
+ } else if (event.actionMasked == MotionEvent.ACTION_CANCEL && doubleTapAwaitingUp) {
+ doubleTapAwaitingUp = false
}
- is MotionEventModel.Move -> {
- if (event.distanceMoved > allowedTouchSlop) {
- logger?.cancelingLongPressDueToTouchSlop(event.distanceMoved, allowedTouchSlop)
+ }
+
+ if (isLongPressHandlingEnabled) {
+ val motionEventModel = event.toModel()
+
+ return when (motionEventModel) {
+ is MotionEventModel.Down -> {
+ scheduleLongPress(motionEventModel.x, motionEventModel.y)
+ lastEventDownCoordinate.x = motionEventModel.x
+ lastEventDownCoordinate.y = motionEventModel.y
+ true
+ }
+
+ is MotionEventModel.Move -> {
+ if (motionEventModel.distanceMoved > allowedTouchSlop) {
+ logger?.cancelingLongPressDueToTouchSlop(
+ motionEventModel.distanceMoved,
+ allowedTouchSlop,
+ )
+ cancelScheduledLongPress()
+ }
+ false
+ }
+
+ is MotionEventModel.Up -> {
+ logger?.onUpEvent(
+ motionEventModel.distanceMoved,
+ allowedTouchSlop,
+ motionEventModel.gestureDuration,
+ )
cancelScheduledLongPress()
+ if (
+ motionEventModel.distanceMoved <= allowedTouchSlop &&
+ motionEventModel.gestureDuration < longPressDuration()
+ ) {
+ logger?.dispatchingSingleTap()
+ dispatchSingleTap(lastEventDownCoordinate.x, lastEventDownCoordinate.y)
+ }
+ false
}
- false
- }
- is MotionEventModel.Up -> {
- logger?.onUpEvent(event.distanceMoved, allowedTouchSlop, event.gestureDuration)
- cancelScheduledLongPress()
- if (
- event.distanceMoved <= allowedTouchSlop &&
- event.gestureDuration < longPressDuration()
- ) {
- logger?.dispatchingSingleTap()
- dispatchSingleTap(lastEventDownCoordinate.x, lastEventDownCoordinate.y)
+
+ is MotionEventModel.Cancel -> {
+ logger?.motionEventCancelled()
+ cancelScheduledLongPress()
+ false
}
- false
- }
- is MotionEventModel.Cancel -> {
- logger?.motionEventCancelled()
- cancelScheduledLongPress()
- false
+
+ else -> false
}
- else -> false
}
+
+ return false
}
private fun scheduleLongPress(x: Int, y: Int) {
@@ -134,4 +194,30 @@ class TouchHandlingViewInteractionHandler(
onSingleTapDetected(x, y)
}
+
+ private fun MotionEvent.toModel(): MotionEventModel {
+ return when (actionMasked) {
+ MotionEvent.ACTION_DOWN -> MotionEventModel.Down(x = x.toInt(), y = y.toInt())
+ MotionEvent.ACTION_MOVE -> MotionEventModel.Move(distanceMoved = distanceMoved())
+ MotionEvent.ACTION_UP ->
+ MotionEventModel.Up(
+ distanceMoved = distanceMoved(),
+ gestureDuration = gestureDuration(),
+ )
+ MotionEvent.ACTION_CANCEL -> MotionEventModel.Cancel
+ else -> MotionEventModel.Other
+ }
+ }
+
+ private fun MotionEvent.distanceMoved(): Float {
+ return if (historySize > 0) {
+ sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
+ } else {
+ 0f
+ }
+ }
+
+ private fun MotionEvent.gestureDuration(): Long {
+ return eventTime - downTime
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt
new file mode 100644
index 000000000000..6a611ec5b647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@SysUISingleton
+class CommunalSuppressionStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val suppressionFlows: Set<@JvmSuppressWildcards Flow<SuppressionReason?>>,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ @CommunalTableLog private val tableLogBuffer: TableLogBuffer,
+) : CoreStartable {
+ override fun start() {
+ getSuppressionReasons()
+ .onEach { reasons -> communalSettingsInteractor.setSuppressionReasons(reasons) }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "suppressionReasons",
+ initialValue = emptyList(),
+ )
+ .flowOn(bgDispatcher)
+ .launchIn(applicationScope)
+ }
+
+ private fun getSuppressionReasons(): Flow<List<SuppressionReason>> {
+ if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
+ return flowOf(listOf(SuppressionReason.ReasonFlagDisabled))
+ }
+ return combine(suppressionFlows) { reasons -> reasons.filterNotNull() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
index e456310febfd..4be9601f4277 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
@@ -102,7 +102,7 @@ public class DeviceInactiveCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index bb3be531aa8a..a31c0bd35453 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -29,6 +29,7 @@ import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositor
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
+import com.android.systemui.communal.domain.suppression.dagger.CommunalSuppressionModule
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -70,6 +71,7 @@ import kotlinx.coroutines.CoroutineScope
CommunalSmartspaceRepositoryModule::class,
CommunalStartableModule::class,
GlanceableHubWidgetManagerModule::class,
+ CommunalSuppressionModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 7358aa7b3fcd..a4f75e81b6ae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -22,6 +22,7 @@ import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalMetricsStartable
import com.android.systemui.communal.CommunalOngoingContentStartable
import com.android.systemui.communal.CommunalSceneStartable
+import com.android.systemui.communal.CommunalSuppressionStartable
import com.android.systemui.communal.DevicePosturingListener
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -73,4 +74,9 @@ interface CommunalStartableModule {
@IntoMap
@ClassKey(DevicePosturingListener::class)
fun bindDevicePosturingistener(impl: DevicePosturingListener): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalSuppressionStartable::class)
+ fun bindCommunalSuppressionStartable(impl: CommunalSuppressionStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
deleted file mode 100644
index 83a5bdb14ebd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.model
-
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-import java.util.EnumSet
-
-/** Reasons that communal is disabled, primarily for logging. */
-enum class DisabledReason(val loggingString: String) {
- /** Communal should be disabled due to invalid current user */
- DISABLED_REASON_INVALID_USER("invalidUser"),
- /** Communal should be disabled due to the flag being off */
- DISABLED_REASON_FLAG("flag"),
- /** Communal should be disabled because the user has turned off the setting */
- DISABLED_REASON_USER_SETTING("userSetting"),
- /** Communal is disabled by the device policy app */
- DISABLED_REASON_DEVICE_POLICY("devicePolicy"),
-}
-
-/**
- * Model representing the reasons communal hub should be disabled. Allows logging reasons separately
- * for debugging.
- */
-@JvmInline
-value class CommunalEnabledState(
- private val disabledReasons: EnumSet<DisabledReason> =
- EnumSet.noneOf(DisabledReason::class.java)
-) : Diffable<CommunalEnabledState>, Set<DisabledReason> by disabledReasons {
-
- /** Creates [CommunalEnabledState] with a single reason for being disabled */
- constructor(reason: DisabledReason) : this(EnumSet.of(reason))
-
- /** Checks if there are any reasons communal should be disabled. If none, returns true. */
- val enabled: Boolean
- get() = isEmpty()
-
- override fun logDiffs(prevVal: CommunalEnabledState, row: TableRowLogger) {
- for (reason in DisabledReason.entries) {
- val newVal = contains(reason)
- if (newVal != prevVal.contains(reason)) {
- row.logChange(
- columnName = reason.loggingString,
- value = newVal,
- )
- }
- }
- }
-
- override fun logFull(row: TableRowLogger) {
- for (reason in DisabledReason.entries) {
- row.logChange(columnName = reason.loggingString, value = contains(reason))
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt
new file mode 100644
index 000000000000..5fb1c4e84eef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.model
+
+import android.annotation.IntDef
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ flag = true,
+ prefix = ["FEATURE_"],
+ value = [FEATURE_AUTO_OPEN, FEATURE_MANUAL_OPEN, FEATURE_ENABLED, FEATURE_ALL],
+)
+annotation class CommunalFeature
+
+/** If we should automatically open the hub */
+const val FEATURE_AUTO_OPEN: Int = 1
+
+/** If the user is allowed to manually open the hub */
+const val FEATURE_MANUAL_OPEN: Int = 1 shl 1
+
+/**
+ * If the hub should be considered enabled. If not, it may be cleaned up entirely to reduce memory
+ * footprint.
+ */
+const val FEATURE_ENABLED: Int = 1 shl 2
+
+const val FEATURE_ALL: Int = FEATURE_ENABLED or FEATURE_MANUAL_OPEN or FEATURE_AUTO_OPEN
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt
new file mode 100644
index 000000000000..de05bed7ef57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.model
+
+sealed interface SuppressionReason {
+ @CommunalFeature val suppressedFeatures: Int
+
+ /** Whether this reason suppresses a particular feature. */
+ fun isSuppressed(@CommunalFeature feature: Int): Boolean {
+ return (suppressedFeatures and feature) != 0
+ }
+
+ /** Suppress hub automatically opening due to Android Auto projection */
+ data object ReasonCarProjection : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_AUTO_OPEN
+ }
+
+ /** Suppress hub due to the "When to dream" conditions not being met */
+ data class ReasonWhenToAutoShow(override val suppressedFeatures: Int) : SuppressionReason
+
+ /** Suppress hub due to device policy */
+ data object ReasonDevicePolicy : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to the user disabling the setting */
+ data object ReasonSettingDisabled : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to the user being locked */
+ data object ReasonUserLocked : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due the a secondary user being active */
+ data object ReasonSecondaryUser : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to the flag being disabled */
+ data object ReasonFlagDisabled : SuppressionReason {
+ override val suppressedFeatures: Int = FEATURE_ALL
+ }
+
+ /** Suppress hub due to an unknown reason, used as initial state and in tests */
+ data class ReasonUnknown(override val suppressedFeatures: Int = FEATURE_ALL) :
+ SuppressionReason
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt
new file mode 100644
index 000000000000..4fe641a78d4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.repository
+
+import android.app.UiModeManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+interface CarProjectionRepository {
+ /** Whether car projection is active. */
+ val projectionActive: Flow<Boolean>
+
+ /**
+ * Checks the system for the current car projection state.
+ *
+ * @return True if projection is active, false otherwise.
+ */
+ suspend fun isProjectionActive(): Boolean
+}
+
+@SysUISingleton
+class CarProjectionRepositoryImpl
+@Inject
+constructor(
+ private val uiModeManager: UiModeManager,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) : CarProjectionRepository {
+ override val projectionActive: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val listener =
+ UiModeManager.OnProjectionStateChangedListener { _, _ -> trySend(Unit) }
+ uiModeManager.addOnProjectionStateChangedListener(
+ UiModeManager.PROJECTION_TYPE_AUTOMOTIVE,
+ bgDispatcher.asExecutor(),
+ listener,
+ )
+ awaitClose { uiModeManager.removeOnProjectionStateChangedListener(listener) }
+ }
+ .emitOnStart()
+ .map { isProjectionActive() }
+ .flowOn(bgDispatcher)
+
+ override suspend fun isProjectionActive(): Boolean =
+ withContext(bgDispatcher) {
+ (uiModeManager.activeProjectionTypes and UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
index 7f137f3b976b..0d590db97860 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
@@ -22,4 +22,6 @@ import dagger.Module
@Module
interface CommunalRepositoryModule {
@Binds fun communalRepository(impl: CommunalSceneRepositoryImpl): CommunalSceneRepository
+
+ @Binds fun carProjectionRepository(impl: CarProjectionRepositoryImpl): CarProjectionRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 4c291a0c5a2e..6f688d172843 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -26,12 +26,9 @@ import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.communal.data.model.CommunalEnabledState
-import com.android.systemui.communal.data.model.DisabledReason
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.communal.data.model.CommunalFeature
+import com.android.systemui.communal.data.model.FEATURE_ALL
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.WhenToDream
@@ -43,22 +40,23 @@ import com.android.systemui.flags.Flags
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import java.util.EnumSet
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
interface CommunalSettingsRepository {
- /** A [CommunalEnabledState] for the specified user. */
- fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
+ /** Whether a particular feature is enabled */
+ fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean>
- fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
+ /**
+ * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
+ * suppressed.
+ */
+ fun setSuppressionReasons(reasons: List<SuppressionReason>)
/**
* Returns a [WhenToDream] for the specified user, indicating what state the device should be in
@@ -66,6 +64,9 @@ interface CommunalSettingsRepository {
*/
fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream>
+ /** Returns whether glanceable hub is enabled by the current user. */
+ fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean>
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
@@ -123,6 +124,19 @@ constructor(
resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault)
}
+ private val _suppressionReasons =
+ MutableStateFlow<List<SuppressionReason>>(
+ // Suppress hub by default until we get an initial update.
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_ALL))
+ )
+
+ override fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean> =
+ _suppressionReasons.map { reasons -> reasons.none { it.isSuppressed(feature) } }
+
+ override fun setSuppressionReasons(reasons: List<SuppressionReason>) {
+ _suppressionReasons.value = reasons
+ }
+
override fun getFlagEnabled(): Boolean {
return if (getV2FlagEnabled()) {
true
@@ -138,44 +152,6 @@ constructor(
glanceableHubV2()
}
- override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
- if (!user.isMain) {
- return flowOf(CommunalEnabledState(DISABLED_REASON_INVALID_USER))
- }
- if (!getFlagEnabled()) {
- return flowOf(CommunalEnabledState(DISABLED_REASON_FLAG))
- }
- return combine(
- getEnabledByUser(user).mapToReason(DISABLED_REASON_USER_SETTING),
- getAllowedByDevicePolicy(user).mapToReason(DISABLED_REASON_DEVICE_POLICY),
- ) { reasons ->
- reasons.filterNotNull()
- }
- .map { reasons ->
- if (reasons.isEmpty()) {
- EnumSet.noneOf(DisabledReason::class.java)
- } else {
- EnumSet.copyOf(reasons)
- }
- }
- .map { reasons -> CommunalEnabledState(reasons) }
- .flowOn(bgDispatcher)
- }
-
- override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> =
- secureSettings
- .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED))
- // Force an update
- .onStart { emit(Unit) }
- .map {
- secureSettings.getIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
- SCREENSAVER_ENABLED_SETTING_DEFAULT,
- user.id,
- ) == 1
- }
- .flowOn(bgDispatcher)
-
override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> =
secureSettings
.observerFlow(
@@ -247,11 +223,11 @@ constructor(
?: defaultBackgroundType
}
- private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
+ override fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean> =
secureSettings
.observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
// Force an update
- .onStart { emit(Unit) }
+ .emitOnStart()
.map {
secureSettings.getIntForUser(
Settings.Secure.GLANCEABLE_HUB_ENABLED,
@@ -259,17 +235,13 @@ constructor(
user.id,
) == 1
}
+ .flowOn(bgDispatcher)
companion object {
const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
private const val ENABLED_SETTING_DEFAULT = 1
- private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0
}
}
private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean =
(getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0
-
-private fun Flow<Boolean>.mapToReason(reason: DisabledReason) = map { enabled ->
- if (enabled) null else reason
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt
new file mode 100644
index 000000000000..17b61e1c6fdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.CarProjectionRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class CarProjectionInteractor @Inject constructor(repository: CarProjectionRepository) {
+ /** Whether car projection is active. */
+ val projectionActive = repository.projectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt
new file mode 100644
index 000000000000..51df3338a18e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
+
+import com.android.systemui.common.domain.interactor.BatteryInteractor
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SWIPE_TO_HUB
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
+import com.android.systemui.communal.shared.model.WhenToDream
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.retrieveIsDocked
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class CommunalAutoOpenInteractor
+@Inject
+constructor(
+ communalSettingsInteractor: CommunalSettingsInteractor,
+ @Background private val backgroundContext: CoroutineContext,
+ private val batteryInteractor: BatteryInteractor,
+ private val posturingInteractor: PosturingInteractor,
+ private val dockManager: DockManager,
+ @Named(SWIPE_TO_HUB) private val allowSwipeAlways: Boolean,
+) {
+ val shouldAutoOpen: Flow<Boolean> =
+ communalSettingsInteractor.whenToDream
+ .flatMapLatestConflated { whenToDream ->
+ when (whenToDream) {
+ WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+ WhenToDream.WHILE_DOCKED -> {
+ allOf(batteryInteractor.isDevicePluggedIn, dockManager.retrieveIsDocked())
+ }
+ WhenToDream.WHILE_POSTURED -> {
+ allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured)
+ }
+ WhenToDream.NEVER -> flowOf(false)
+ }
+ }
+ .flowOn(backgroundContext)
+
+ val suppressionReason: Flow<SuppressionReason?> =
+ shouldAutoOpen.map { conditionMet ->
+ if (conditionMet) {
+ null
+ } else {
+ var suppressedFeatures = FEATURE_AUTO_OPEN
+ if (!allowSwipeAlways) {
+ suppressedFeatures = suppressedFeatures or FEATURE_MANUAL_OPEN
+ }
+ SuppressionReason.ReasonWhenToAutoShow(suppressedFeatures)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 564628d3f52f..684c52ad45f3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -30,13 +30,11 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.Flags.glanceableHubBlurredBackground
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.domain.interactor.BatteryInteractor
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
-import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
@@ -45,14 +43,11 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -69,11 +64,8 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.ManagedProfileController
-import com.android.systemui.user.domain.interactor.UserLockedInteractor
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.kotlin.isDevicePluggedIn
import javax.inject.Inject
import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.CoroutineDispatcher
@@ -125,10 +117,6 @@ constructor(
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
private val managedProfileController: ManagedProfileController,
- private val batteryInteractor: BatteryInteractor,
- private val dockManager: DockManager,
- private val posturingInteractor: PosturingInteractor,
- private val userLockedInteractor: UserLockedInteractor,
) {
private val logger = Logger(logBuffer, "CommunalInteractor")
@@ -162,11 +150,7 @@ constructor(
/** Whether communal features are enabled and available. */
val isCommunalAvailable: Flow<Boolean> =
- allOf(
- communalSettingsInteractor.isCommunalEnabled,
- userLockedInteractor.isUserUnlocked(userManager.mainUser),
- keyguardInteractor.isKeyguardShowing,
- )
+ allOf(communalSettingsInteractor.isCommunalEnabled, keyguardInteractor.isKeyguardShowing)
.distinctUntilChanged()
.onEach { available ->
logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
@@ -184,37 +168,6 @@ constructor(
replay = 1,
)
- /**
- * Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
- * state.
- */
- val shouldShowCommunal: StateFlow<Boolean> =
- allOf(
- isCommunalAvailable,
- communalSettingsInteractor.whenToDream
- .flatMapLatest { whenToDream ->
- when (whenToDream) {
- WhenToDream.NEVER -> flowOf(false)
-
- WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
-
- WhenToDream.WHILE_DOCKED ->
- allOf(
- batteryInteractor.isDevicePluggedIn,
- dockManager.retrieveIsDocked(),
- )
-
- WhenToDream.WHILE_POSTURED ->
- allOf(
- batteryInteractor.isDevicePluggedIn,
- posturingInteractor.postured,
- )
- }
- }
- .flowOn(bgDispatcher),
- )
- .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
-
private val _isDisclaimerDismissed = MutableStateFlow(false)
val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index a0b1261df346..0d7a2d9707d7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -17,18 +17,19 @@
package com.android.systemui.communal.domain.interactor
import android.content.pm.UserInfo
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_ENABLED
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.dagger.CommunalTableLog
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -53,33 +54,43 @@ constructor(
private val repository: CommunalSettingsRepository,
userInteractor: SelectedUserInteractor,
private val userTracker: UserTracker,
- @CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
- /** Whether or not communal is enabled for the currently selected user. */
+ /** Whether communal is enabled at all. */
val isCommunalEnabled: StateFlow<Boolean> =
- userInteractor.selectedUserInfo
- .flatMapLatest { user -> repository.getEnabledState(user) }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnPrefix = "disabledReason",
- initialValue = CommunalEnabledState(),
- )
- .map { model -> model.enabled }
- // Start this eagerly since the value is accessed synchronously in many places.
+ repository
+ .isEnabled(FEATURE_ENABLED)
.stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
- /** Whether or not screensaver (dreams) is enabled for the currently selected user. */
- val isScreensaverEnabled: Flow<Boolean> =
- userInteractor.selectedUserInfo.flatMapLatest { user ->
- repository.getScreensaverEnabledState(user)
- }
+ /** Whether manually opening the hub is enabled */
+ val manualOpenEnabled: StateFlow<Boolean> =
+ repository
+ .isEnabled(FEATURE_MANUAL_OPEN)
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+
+ /** Whether auto-opening the hub is enabled */
+ val autoOpenEnabled: StateFlow<Boolean> =
+ repository
+ .isEnabled(FEATURE_AUTO_OPEN)
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
/** When to dream for the currently selected user. */
val whenToDream: Flow<WhenToDream> =
- userInteractor.selectedUserInfo.flatMapLatest { user ->
+ userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
repository.getWhenToDreamState(user)
}
+ /** Whether communal hub is allowed by device policy for the current user */
+ val allowedForCurrentUserByDevicePolicy: Flow<Boolean> =
+ userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+ repository.getAllowedByDevicePolicy(user)
+ }
+
+ /** Whether the hub is enabled for the current user */
+ val settingEnabledForCurrentUser: Flow<Boolean> =
+ userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+ repository.getSettingEnabledByUser(user)
+ }
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
@@ -109,6 +120,14 @@ constructor(
*/
fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled()
+ /**
+ * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
+ * suppressed.
+ */
+ fun setSuppressionReasons(reasons: List<SuppressionReason>) {
+ repository.setSuppressionReasons(reasons)
+ }
+
/** The type of background to use for the hub. Used to experiment with different backgrounds */
val communalBackground: Flow<CommunalBackgroundType> =
userInteractor.selectedUserInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt
new file mode 100644
index 000000000000..a10e90f09cc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.suppression
+
+import com.android.systemui.communal.data.model.SuppressionReason
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+fun Flow<Boolean>.mapToReasonIfNotAllowed(reason: SuppressionReason): Flow<SuppressionReason?> =
+ this.map { allowed -> if (allowed) null else reason }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt
new file mode 100644
index 000000000000..c62d77eee287
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.suppression.dagger
+
+import com.android.systemui.Flags.glanceableHubV2
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.domain.interactor.CarProjectionInteractor
+import com.android.systemui.communal.domain.interactor.CommunalAutoOpenInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.domain.suppression.mapToReasonIfNotAllowed
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.user.domain.interactor.UserLockedInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import dagger.multibindings.Multibinds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@Module
+interface CommunalSuppressionModule {
+ /**
+ * A set of reasons why communal may be suppressed. Ensures that this can be injected even if
+ * it's empty.
+ */
+ @Multibinds fun suppressorSet(): Set<Flow<SuppressionReason?>>
+
+ companion object {
+ @Provides
+ @IntoSet
+ fun provideCarProjectionSuppressor(
+ interactor: CarProjectionInteractor
+ ): Flow<SuppressionReason?> {
+ if (!glanceableHubV2()) {
+ return flowOf(null)
+ }
+ return not(interactor.projectionActive)
+ .mapToReasonIfNotAllowed(SuppressionReason.ReasonCarProjection)
+ }
+
+ @Provides
+ @IntoSet
+ fun provideDevicePolicySuppressor(
+ interactor: CommunalSettingsInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.allowedForCurrentUserByDevicePolicy.mapToReasonIfNotAllowed(
+ SuppressionReason.ReasonDevicePolicy
+ )
+ }
+
+ @Provides
+ @IntoSet
+ fun provideSettingDisabledSuppressor(
+ interactor: CommunalSettingsInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.settingEnabledForCurrentUser.mapToReasonIfNotAllowed(
+ SuppressionReason.ReasonSettingDisabled
+ )
+ }
+
+ @Provides
+ @IntoSet
+ fun bindUserLockedSuppressor(interactor: UserLockedInteractor): Flow<SuppressionReason?> {
+ return interactor.currentUserUnlocked.mapToReasonIfNotAllowed(
+ SuppressionReason.ReasonUserLocked
+ )
+ }
+
+ @Provides
+ @IntoSet
+ fun provideAutoOpenSuppressor(
+ interactor: CommunalAutoOpenInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.suppressionReason
+ }
+
+ @Provides
+ @IntoSet
+ fun provideMainUserSuppressor(
+ interactor: SelectedUserInteractor
+ ): Flow<SuppressionReason?> {
+ return interactor.selectedUserInfo
+ .map { it.isMain }
+ .mapToReasonIfNotAllowed(SuppressionReason.ReasonSecondaryUser)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7859c985c7b..756edb3d048d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -247,7 +247,7 @@ constructor(
showsOnlyActiveMedia = false
}
falsingProtectionNeeded = false
- disablePagination = true
+ disableScrolling = true
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
}
}
@@ -367,11 +367,22 @@ constructor(
/** See [CommunalSettingsInteractor.isV2FlagEnabled] */
fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
- val swipeToHubEnabled: StateFlow<Boolean> by lazy {
+ val swipeToHubEnabled: Flow<Boolean> by lazy {
+ val inAllowedDeviceState =
+ if (v2FlagEnabled()) {
+ communalSettingsInteractor.manualOpenEnabled
+ } else {
+ MutableStateFlow(swipeToHub)
+ }
+
if (v2FlagEnabled()) {
- communalInteractor.shouldShowCommunal
+ val inAllowedKeyguardState =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
+ it.to == KeyguardState.LOCKSCREEN || it.to == KeyguardState.GLANCEABLE_HUB
+ }
+ allOf(inAllowedDeviceState, inAllowedKeyguardState)
} else {
- MutableStateFlow(swipeToHub)
+ inAllowedDeviceState
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
index 6f579a3986c8..d7ffbb2e76b8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
@@ -18,7 +18,7 @@
package com.android.systemui.controls.settings
import android.provider.Settings
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
new file mode 100644
index 000000000000..39708a743c23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dagger
+
+import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.model.SysUIStateInstanceProvider
+import com.android.systemui.model.SysUiState
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Module
+import dagger.Provides
+
+/** This module is meant to contain all the code to create the various [PerDisplayRepository<>]. */
+@Module
+class PerDisplayRepositoriesModule {
+
+ @SysUISingleton
+ @Provides
+ fun provideSysUiStateRepository(
+ repositoryFactory: PerDisplayInstanceRepositoryImpl.Factory<SysUiState>,
+ instanceProvider: SysUIStateInstanceProvider,
+ ): PerDisplayRepository<SysUiState> {
+ val debugName = "SysUiStatePerDisplayRepo"
+ return if (ShadeWindowGoesAround.isEnabled) {
+ repositoryFactory.create(debugName, instanceProvider)
+ } else {
+ DefaultDisplayOnlyInstanceRepositoryImpl(debugName, instanceProvider)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a25faa3a7aec..11b42a8eafd6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -36,6 +36,8 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.education.dagger.ContextualEducationModule;
+import com.android.systemui.topwindoweffects.dagger.SqueezeEffectRepositoryModule;
+import com.android.systemui.topwindoweffects.dagger.TopLevelWindowEffectsModule;
import com.android.systemui.emergency.EmergencyGestureModule;
import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialModule;
import com.android.systemui.keyboard.shortcut.ShortcutHelperModule;
@@ -160,12 +162,14 @@ import javax.inject.Named;
StatusBarPhoneModule.class,
SystemActionsModule.class,
ShadeModule.class,
+ SqueezeEffectRepositoryModule.class,
StartCentralSurfacesModule.class,
SceneContainerFrameworkModule.class,
SysUICoroutinesModule.class,
SysUIUnfoldStartableModule.class,
UnfoldTransitionModule.Startables.class,
ToastModule.class,
+ TopLevelWindowEffectsModule.class,
TouchpadTutorialModule.class,
VolumeModule.class,
WallpaperModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f8cf6b007041..f08126af0a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -21,6 +21,7 @@ import android.app.Service;
import android.app.backup.BackupManager;
import android.content.Context;
import android.service.dreams.IDreamManager;
+import android.view.Display;
import androidx.annotation.Nullable;
@@ -64,9 +65,9 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
+import com.android.systemui.display.data.repository.PerDisplayRepository;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
@@ -86,7 +87,6 @@ import com.android.systemui.mediaprojection.MediaProjectionModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule;
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.mediarouter.MediaRouterModule;
-import com.android.systemui.model.SceneContainerPlugin;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -113,7 +113,6 @@ import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.screenrecord.ScreenRecordModule;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
-import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeDisplayAwareModule;
@@ -289,7 +288,8 @@ import javax.inject.Named;
UtilModule.class,
NoteTaskModule.class,
WalletModule.class,
- LowLightModule.class
+ LowLightModule.class,
+ PerDisplayRepositoriesModule.class
},
subcomponents = {
ComplicationComponent.class,
@@ -326,12 +326,8 @@ public abstract class SystemUIModule {
@SysUISingleton
@Provides
static SysUiState provideSysUiState(
- DisplayTracker displayTracker,
- DumpManager dumpManager,
- SceneContainerPlugin sceneContainerPlugin) {
- final SysUiState state = new SysUiState(displayTracker, sceneContainerPlugin);
- dumpManager.registerDumpable(state);
- return state;
+ PerDisplayRepository<SysUiState> repository) {
+ return repository.get(Display.DEFAULT_DISPLAY);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
index fa5556d44674..cedd5161a777 100644
--- a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
@@ -23,6 +23,7 @@ import android.os.Build
import android.os.UserHandle
import com.android.internal.R as InternalR
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.development.data.repository.DevelopmentSettingRepository
@@ -32,9 +33,12 @@ import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.utils.UserScopedService
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -46,6 +50,7 @@ constructor(
private val userRepository: UserRepository,
private val clipboardManagerProvider: UserScopedService<ClipboardManager>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope,
) {
/**
@@ -53,10 +58,11 @@ constructor(
*
* @see DevelopmentSettingRepository.isDevelopmentSettingEnabled
*/
- val buildNumber: Flow<BuildNumber?> =
+ val buildNumber: StateFlow<BuildNumber?> =
userRepository.selectedUserInfo
.flatMapConcat { userInfo -> repository.isDevelopmentSettingEnabled(userInfo) }
.map { enabled -> buildText.takeIf { enabled } }
+ .stateIn(applicationScope, WhileSubscribed(), null)
private val buildText =
BuildNumber(
diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
index 68c51ea80ffd..31d0471f55e2 100644
--- a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
@@ -41,7 +41,6 @@ constructor(private val buildNumberInteractor: BuildNumberInteractor) : Exclusiv
val buildNumber: BuildNumber? by
hydrator.hydratedStateOf(
traceName = "buildNumber",
- initialValue = null,
source = buildNumberInteractor.buildNumber,
)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 69378b475938..449a995b782a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -27,7 +27,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 675f00a89d23..b7315cc994a8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -1,7 +1,7 @@
package com.android.systemui.deviceentry.data.repository
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
index 69da67e055fe..1e7bec257432 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -68,10 +68,4 @@ constructor(
emptyFlow()
}
}
-
- /** Triggered if a face failure occurs regardless of the mode. */
- val faceFailure: Flow<FailedFaceAuthenticationStatus> =
- deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance<
- FailedFaceAuthenticationStatus
- >()
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 38e0503440f9..09936839c590 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -49,8 +48,6 @@ class DeviceEntryHapticsInteractor
constructor(
biometricSettingsRepository: BiometricSettingsRepository,
deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
- deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
- keyguardBypassInteractor: KeyguardBypassInteractor,
deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -83,7 +80,12 @@ constructor(
emit(recentPowerButtonPressThresholdMs * -1L - 1L)
}
- private val playHapticsOnDeviceEntry: Flow<Boolean> =
+ /**
+ * Indicates when success haptics should play when the device is entered. This always occurs on
+ * successful fingerprint authentications. It also occurs on successful face authentication but
+ * only if the lockscreen is bypassed.
+ */
+ val playSuccessHapticOnDeviceEntry: Flow<Unit> =
deviceEntrySourceInteractor.deviceEntryFromBiometricSource
.sample(
combine(
@@ -93,29 +95,17 @@ constructor(
::Triple,
)
)
- .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+ .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
val sideFpsAllowsHaptic =
!powerButtonDown &&
systemClock.uptimeMillis() - lastPowerButtonWakeup >
recentPowerButtonPressThresholdMs
val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
if (!allowHaptic) {
- logger.d(
- "Skip success entry haptic from power button. Recent power button press or button is down."
- )
+ logger.d("Skip success haptic. Recent power button press or button is down.")
}
allowHaptic
}
-
- private val playHapticsOnFaceAuthSuccessAndBypassDisabled: Flow<Boolean> =
- deviceEntryFaceAuthInteractor.isAuthenticated
- .filter { it }
- .sample(keyguardBypassInteractor.isBypassAvailable)
- .map { !it }
-
- val playSuccessHaptic: Flow<Unit> =
- merge(playHapticsOnDeviceEntry, playHapticsOnFaceAuthSuccessAndBypassDisabled)
- .filter { it }
// map to Unit
.map {}
.dumpWhileCollecting("playSuccessHaptic")
@@ -123,7 +113,7 @@ constructor(
private val playErrorHapticForBiometricFailure: Flow<Unit> =
merge(
deviceEntryFingerprintAuthInteractor.fingerprintFailure,
- deviceEntryBiometricAuthInteractor.faceFailure,
+ deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
)
// map to Unit
.map {}
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 9b181be93b61..f3316958f01d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -16,8 +16,13 @@
package com.android.systemui.display
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import com.android.app.displaylib.DisplayLibComponent
+import com.android.app.displaylib.createDisplayLibComponent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
import com.android.systemui.display.data.repository.DisplayRepository
@@ -28,6 +33,8 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper
+import com.android.systemui.display.data.repository.PerDisplayRepository
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule
@@ -40,9 +47,11 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
/** Module binding display related classes. */
-@Module(includes = [DisplayWindowPropertiesInteractorModule::class])
+@Module(includes = [DisplayWindowPropertiesInteractorModule::class, DisplayLibModule::class])
interface DisplayModule {
@Binds
fun bindConnectedDisplayInteractor(
@@ -73,6 +82,9 @@ interface DisplayModule {
impl: DisplayWindowPropertiesRepositoryImpl
): DisplayWindowPropertiesRepository
+ @Binds
+ fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback
+
companion object {
@Provides
@SysUISingleton
@@ -103,3 +115,31 @@ interface DisplayModule {
}
}
}
+
+/** Module to bind the DisplayRepository from displaylib to the systemui dagger graph. */
+@Module
+object DisplayLibModule {
+ @Provides
+ @SysUISingleton
+ fun displayLibComponent(
+ displayManager: DisplayManager,
+ @Background backgroundHandler: Handler,
+ @Background bgApplicationScope: CoroutineScope,
+ @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
+ ): DisplayLibComponent {
+ return createDisplayLibComponent(
+ displayManager,
+ backgroundHandler,
+ bgApplicationScope,
+ backgroundCoroutineDispatcher,
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ fun providesDisplayRepositoryFromLib(
+ displayLibComponent: DisplayLibComponent
+ ): com.android.app.displaylib.DisplayRepository {
+ return displayLibComponent.displayRepository
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
index 29044d017d2d..f4db2cc71b38 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
@@ -27,7 +27,7 @@ import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFI
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.feature.flags.Flags as DeviceStateManagerFlags
import com.android.internal.R
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
import java.util.concurrent.Executor
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
index cef45dcae43e..3c554b9ff66b 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -19,7 +19,7 @@ package com.android.systemui.display.data.repository
import android.content.Context
import android.content.res.Configuration
import android.util.DisplayMetrics
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 721d116004f3..051fe7e5517c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -17,112 +17,30 @@
package com.android.systemui.display.data.repository
import android.annotation.SuppressLint
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED
-import android.hardware.display.DisplayManager.DisplayListener
-import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_ADDED
-import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_CHANGED
-import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_REMOVED
-import android.os.Handler
-import android.util.Log
-import android.view.Display
import android.view.IWindowManager
-import com.android.app.tracing.FlowTracing.traceEach
-import com.android.app.tracing.traceSection
+import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.display.data.DisplayEvent
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.Compile
-import com.android.systemui.util.kotlin.pairwiseBy
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
/** Repository for providing access to display related information and events. */
-interface DisplayRepository {
- /** Display change event indicating a change to the given displayId has occurred. */
- val displayChangeEvent: Flow<Int>
-
- /** Display addition event indicating a new display has been added. */
- val displayAdditionEvent: Flow<Display?>
-
- /** Display removal event indicating a display has been removed. */
- val displayRemovalEvent: Flow<Int>
+interface DisplayRepository : DisplayRepositoryFromLib {
/** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
-
- /**
- * Provides the current set of displays.
- *
- * Consider using [displayIds] if only the [Display.getDisplayId] is needed.
- */
- val displays: StateFlow<Set<Display>>
-
- /**
- * Provides the current set of display ids.
- *
- * Note that it is preferred to use this instead of [displays] if only the
- * [Display.getDisplayId] is needed.
- */
- val displayIds: StateFlow<Set<Int>>
-
- /**
- * Pending display id that can be enabled/disabled.
- *
- * When `null`, it means there is no pending display waiting to be enabled.
- */
- val pendingDisplay: Flow<PendingDisplay?>
-
- /** Whether the default display is currently off. */
- val defaultDisplayOff: Flow<Boolean>
-
- /**
- * Given a display ID int, return the corresponding Display object, or null if none exist.
- *
- * This method is guaranteed to not result in any binder call.
- */
- fun getDisplay(displayId: Int): Display? =
- displays.value.firstOrNull { it.displayId == displayId }
-
- /** Represents a connected display that has not been enabled yet. */
- interface PendingDisplay {
- /** Id of the pending display. */
- val id: Int
-
- /** Enables the display, making it available to the system. */
- suspend fun enable()
-
- /**
- * Ignores the pending display. When called, this specific display id doesn't appear as
- * pending anymore until the display is disconnected and reconnected again.
- */
- suspend fun ignore()
-
- /** Disables the display, making it unavailable to the system. */
- suspend fun disable()
- }
}
@SysUISingleton
@@ -130,310 +48,11 @@ interface DisplayRepository {
class DisplayRepositoryImpl
@Inject
constructor(
- private val displayManager: DisplayManager,
private val commandQueue: CommandQueue,
private val windowManager: IWindowManager,
- @Background backgroundHandler: Handler,
@Background bgApplicationScope: CoroutineScope,
- @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
-) : DisplayRepository {
- private val allDisplayEvents: Flow<DisplayEvent> =
- conflatedCallbackFlow {
- val callback =
- object : DisplayListener {
- override fun onDisplayAdded(displayId: Int) {
- trySend(DisplayEvent.Added(displayId))
- }
-
- override fun onDisplayRemoved(displayId: Int) {
- trySend(DisplayEvent.Removed(displayId))
- }
-
- override fun onDisplayChanged(displayId: Int) {
- trySend(DisplayEvent.Changed(displayId))
- }
- }
- displayManager.registerDisplayListener(
- callback,
- backgroundHandler,
- EVENT_TYPE_DISPLAY_ADDED or
- EVENT_TYPE_DISPLAY_CHANGED or
- EVENT_TYPE_DISPLAY_REMOVED,
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
- .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
- .debugLog("allDisplayEvents")
- .flowOn(backgroundCoroutineDispatcher)
-
- override val displayChangeEvent: Flow<Int> =
- allDisplayEvents.filterIsInstance<DisplayEvent.Changed>().map { event -> event.displayId }
-
- override val displayRemovalEvent: Flow<Int> =
- allDisplayEvents.filterIsInstance<DisplayEvent.Removed>().map { it.displayId }
-
- // This is necessary because there might be multiple displays, and we could
- // have missed events for those added before this process or flow started.
- // Note it causes a binder call from the main thread (it's traced).
- private val initialDisplays: Set<Display> =
- traceSection("$TAG#initialDisplays") { displayManager.displays?.toSet() ?: emptySet() }
- private val initialDisplayIds = initialDisplays.map { display -> display.displayId }.toSet()
-
- /** Propagate to the listeners only enabled displays */
- private val enabledDisplayIds: StateFlow<Set<Int>> =
- allDisplayEvents
- .scan(initial = initialDisplayIds) { previousIds: Set<Int>, event: DisplayEvent ->
- val id = event.displayId
- when (event) {
- is DisplayEvent.Removed -> previousIds - id
- is DisplayEvent.Added,
- is DisplayEvent.Changed -> previousIds + id
- }
- }
- .distinctUntilChanged()
- .debugLog("enabledDisplayIds")
- .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
-
- private val defaultDisplay by lazy {
- getDisplayFromDisplayManager(Display.DEFAULT_DISPLAY)
- ?: error("Unable to get default display.")
- }
-
- /**
- * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
- *
- * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
- */
- private val enabledDisplays: StateFlow<Set<Display>> =
- enabledDisplayIds
- .mapElementsLazily { displayId -> getDisplayFromDisplayManager(displayId) }
- .onEach {
- if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.")
- }
- .flowOn(backgroundCoroutineDispatcher)
- .debugLog("enabledDisplays")
- .stateIn(
- bgApplicationScope,
- started = SharingStarted.WhileSubscribed(),
- // This triggers a single binder call on the UI thread per process. The
- // alternative would be to use sharedFlows, but they are prohibited due to
- // performance concerns.
- // Ultimately, this is a trade-off between a one-time UI thread binder call and
- // the constant overhead of sharedFlows.
- initialValue = initialDisplays,
- )
-
- /**
- * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
- *
- * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
- */
- override val displays: StateFlow<Set<Display>> = enabledDisplays
-
- override val displayIds: StateFlow<Set<Int>> = enabledDisplayIds
-
- /**
- * Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons:
- * 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that
- * calling [getDisplay] for the newly added display will be non-null.
- * 2. Reuse the existing instance of [Display] without a new call to [DisplayManager].
- */
- override val displayAdditionEvent: Flow<Display?> =
- displays
- .pairwiseBy { previousDisplays, currentDisplays -> currentDisplays - previousDisplays }
- .flatMapLatest { it.asFlow() }
-
- val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
- private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds")
-
- private fun getInitialConnectedDisplays(): Set<Int> =
- traceSection("$TAG#getInitialConnectedDisplays") {
- displayManager
- .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
- .map { it.displayId }
- .toSet()
- .also {
- if (DEBUG) {
- Log.d(TAG, "getInitialConnectedDisplays: $it")
- }
- }
- }
-
- /* keeps connected displays until they are disconnected. */
- private val connectedDisplayIds: StateFlow<Set<Int>> =
- conflatedCallbackFlow {
- val connectedIds = getInitialConnectedDisplays().toMutableSet()
- val callback =
- object : DisplayConnectionListener {
- override fun onDisplayConnected(id: Int) {
- if (DEBUG) {
- Log.d(TAG, "display with id=$id connected.")
- }
- connectedIds += id
- _ignoredDisplayIds.value -= id
- trySend(connectedIds.toSet())
- }
-
- override fun onDisplayDisconnected(id: Int) {
- connectedIds -= id
- if (DEBUG) {
- Log.d(TAG, "display with id=$id disconnected.")
- }
- _ignoredDisplayIds.value -= id
- trySend(connectedIds.toSet())
- }
- }
- trySend(connectedIds.toSet())
- displayManager.registerDisplayListener(
- callback,
- backgroundHandler,
- /* eventFlags */ 0,
- DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED,
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
- .distinctUntilChanged()
- .debugLog("connectedDisplayIds")
- .stateIn(
- bgApplicationScope,
- started = SharingStarted.WhileSubscribed(),
- // The initial value is set to empty, but connected displays are gathered as soon as
- // the flow starts being collected. This is to ensure the call to get displays (an
- // IPC) happens in the background instead of when this object
- // is instantiated.
- initialValue = emptySet(),
- )
-
- private val connectedExternalDisplayIds: Flow<Set<Int>> =
- connectedDisplayIds
- .map { connectedDisplayIds ->
- traceSection("$TAG#filteringExternalDisplays") {
- connectedDisplayIds
- .filter { id -> getDisplayType(id) == Display.TYPE_EXTERNAL }
- .toSet()
- }
- }
- .flowOn(backgroundCoroutineDispatcher)
- .debugLog("connectedExternalDisplayIds")
-
- private fun getDisplayType(displayId: Int): Int? =
- traceSection("$TAG#getDisplayType") { displayManager.getDisplay(displayId)?.type }
-
- private fun getDisplayFromDisplayManager(displayId: Int): Display? =
- traceSection("$TAG#getDisplay") { displayManager.getDisplay(displayId) }
-
- /**
- * Pending displays are the ones connected, but not enabled and not ignored.
- *
- * A connected display is ignored after the user makes the decision to use it or not. For now,
- * the initial decision from the user is final and not reversible.
- */
- private val pendingDisplayIds: Flow<Set<Int>> =
- combine(enabledDisplayIds, connectedExternalDisplayIds, ignoredDisplayIds) {
- enabledDisplaysIds,
- connectedExternalDisplayIds,
- ignoredDisplayIds ->
- if (DEBUG) {
- Log.d(
- TAG,
- "combining enabled=$enabledDisplaysIds, " +
- "connectedExternalDisplayIds=$connectedExternalDisplayIds, " +
- "ignored=$ignoredDisplayIds",
- )
- }
- connectedExternalDisplayIds - enabledDisplaysIds - ignoredDisplayIds
- }
- .debugLog("allPendingDisplayIds")
-
- /** Which display id should be enabled among the pending ones. */
- private val pendingDisplayId: Flow<Int?> =
- pendingDisplayIds.map { it.maxOrNull() }.distinctUntilChanged().debugLog("pendingDisplayId")
-
- override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> =
- pendingDisplayId
- .map { displayId ->
- val id = displayId ?: return@map null
- object : DisplayRepository.PendingDisplay {
- override val id = id
-
- override suspend fun enable() {
- traceSection("DisplayRepository#enable($id)") {
- if (DEBUG) {
- Log.d(TAG, "Enabling display with id=$id")
- }
- displayManager.enableConnectedDisplay(id)
- }
- // After the display has been enabled, it is automatically ignored.
- ignore()
- }
-
- override suspend fun ignore() {
- traceSection("DisplayRepository#ignore($id)") {
- _ignoredDisplayIds.value += id
- }
- }
-
- override suspend fun disable() {
- ignore()
- traceSection("DisplayRepository#disable($id)") {
- if (DEBUG) {
- Log.d(TAG, "Disabling display with id=$id")
- }
- displayManager.disableConnectedDisplay(id)
- }
- }
- }
- }
- .debugLog("pendingDisplay")
-
- override val defaultDisplayOff: Flow<Boolean> =
- displayChangeEvent
- .filter { it == Display.DEFAULT_DISPLAY }
- .map { defaultDisplay.state == Display.STATE_OFF }
- .distinctUntilChanged()
-
- private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
- return if (DEBUG) {
- traceEach(flowName, logcat = true, traceEmissionCount = true)
- } else {
- this
- }
- }
-
- /**
- * Maps a set of T to a set of V, minimizing the number of `createValue` calls taking into
- * account the diff between each root flow emission.
- *
- * This is needed to minimize the number of [getDisplayFromDisplayManager] in this class. Note
- * that if the [createValue] returns a null element, it will not be added in the output set.
- */
- private fun <T, V> Flow<Set<T>>.mapElementsLazily(createValue: (T) -> V?): Flow<Set<V>> {
- data class State<T, V>(
- val previousSet: Set<T>,
- // Caches T values from the previousSet that were already converted to V
- val valueMap: Map<T, V>,
- val resultSet: Set<V>,
- )
-
- val emptyInitialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
- return this.scan(emptyInitialState) { state, currentSet ->
- if (currentSet == state.previousSet) {
- state
- } else {
- val removed = state.previousSet - currentSet
- val added = currentSet - state.previousSet
- val newMap = state.valueMap.toMutableMap()
-
- added.forEach { key -> createValue(key)?.let { newMap[key] = it } }
- removed.forEach { key -> newMap.remove(key) }
-
- val resultSet = newMap.values.toSet()
- State(currentSet, newMap, resultSet)
- }
- }
- .filter { it != emptyInitialState }
- .map { it.resultSet }
- }
+ private val displayRepositoryFromLib: com.android.app.displaylib.DisplayRepository,
+) : DisplayRepositoryFromLib by displayRepositoryFromLib, DisplayRepository {
private val decorationEvents: Flow<Event> = callbackFlow {
val callback =
@@ -487,20 +106,5 @@ constructor(
private companion object {
const val TAG = "DisplayRepository"
- val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
}
}
-
-/** Used to provide default implementations for all methods. */
-private interface DisplayConnectionListener : DisplayListener {
-
- override fun onDisplayConnected(id: Int) {}
-
- override fun onDisplayDisconnected(id: Int) {}
-
- override fun onDisplayAdded(id: Int) {}
-
- override fun onDisplayRemoved(id: Int) {}
-
- override fun onDisplayChanged(id: Int) {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
index f4d256a5b2ba..d912b6a13d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
@@ -53,7 +53,7 @@ constructor(
}
override fun start() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
backgroundApplicationScope.launch {
displayRepository.displayRemovalEvent.collect { displayId ->
val scope = perDisplayScopes.remove(displayId)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
new file mode 100644
index 000000000000..a56710ee3772
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.display.data.repository
+
+class FakePerDisplayRepository<T> : PerDisplayRepository<T> {
+
+ private val instances = mutableMapOf<Int, T>()
+
+ fun add(displayId: Int, instance: T) {
+ instances[displayId] = instance
+ }
+
+ fun remove(displayId: Int) {
+ instances.remove(displayId)
+ }
+
+ override fun get(displayId: Int): T? {
+ return instances[displayId]
+ }
+
+ override val debugName: String
+ get() = "FakePerDisplayRepository"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
new file mode 100644
index 000000000000..212d55612935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.display.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpableFromToString
+import javax.inject.Inject
+
+/** Helper class to register PerDisplayRepository in the dump manager in SystemUI. */
+@SysUISingleton
+class PerDisplayRepoDumpHelper @Inject constructor(private val dumpManager: DumpManager) :
+ PerDisplayRepository.InitCallback {
+ /**
+ * Registers PerDisplayRepository in the dump manager.
+ *
+ * The repository will be identified by the given debug name.
+ */
+ override fun onInit(debugName: String, instance: Any) {
+ dumpManager.registerNormalDumpable(
+ "PerDisplayRepository-$debugName",
+ DumpableFromToString(instance),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
new file mode 100644
index 000000000000..7e00c60dc43a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.display.data.repository
+
+import android.util.Log
+import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Used to create instances of type `T` for a specific display.
+ *
+ * This is useful for resources or objects that need to be managed independently for each connected
+ * display (e.g., UI state, rendering contexts, or display-specific configurations).
+ *
+ * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId`
+ * parameter
+ *
+ * ```kotlin
+ * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..)
+ * @AssistedFactory
+ * interface Factory {
+ * fun create(displayId: Int): SomeType
+ * }
+ * }
+ * ```
+ *
+ * Then it can be used to create a [PerDisplayRepository] as follows:
+ * ```kotlin
+ * // Injected:
+ * val repositoryFactory: PerDisplayRepositoryImpl.Factory
+ * val instanceFactory: PerDisplayRepositoryImpl.Factory
+ * // repository creation:
+ * repositoryFactory.create(instanceFactory::create)
+ * ```
+ *
+ * @see PerDisplayRepository For how to retrieve and manage instances created by this factory.
+ */
+fun interface PerDisplayInstanceProvider<T> {
+ /** Creates an instance for a display. */
+ fun createInstance(displayId: Int): T?
+}
+
+/**
+ * Extends [PerDisplayInstanceProvider], adding support for destroying the instance.
+ *
+ * This is useful for releasing resources associated with a display when it is disconnected or when
+ * the per-display instance is no longer needed.
+ */
+interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> {
+ /** Destroys a previously created instance of `T` forever. */
+ fun destroyInstance(instance: T)
+}
+
+/**
+ * Provides access to per-display instances of type `T`.
+ *
+ * Acts as a repository, managing the caching and retrieval of instances created by a
+ * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID.
+ */
+interface PerDisplayRepository<T> {
+ /** Gets the cached instance or create a new one for a given display. */
+ operator fun get(displayId: Int): T?
+
+ /** Debug name for this repository, mainly for tracing and logging. */
+ val debugName: String
+
+ /**
+ * Callback to run when a given repository is initialized.
+ *
+ * This allows the caller to perform custom logic when the repository is ready to be used, e.g.
+ * register to dumpManager.
+ *
+ * Note that the instance is *leaked* outside of this class, so it should only be done when
+ * repository is meant to live as long as the caller. In systemUI this is ok because the
+ * repository lives as long as the process itself.
+ */
+ interface InitCallback {
+ fun onInit(debugName: String, instance: Any)
+ }
+}
+
+/**
+ * Default implementation of [PerDisplayRepository].
+ *
+ * This class manages a cache of per-display instances of type `T`, creating them using a provided
+ * [PerDisplayInstanceProvider] and optionally tearing them down using a
+ * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected.
+ *
+ * It listens to the [DisplayRepository] to detect when displays are added or removed, and
+ * automatically manages the lifecycle of the per-display instances.
+ *
+ * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings,
+ * providing all args in the constructor.
+ */
+class PerDisplayInstanceRepositoryImpl<T>
+@AssistedInject
+constructor(
+ @Assisted override val debugName: String,
+ @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>,
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+ private val initCallback: PerDisplayRepository.InitCallback,
+) : PerDisplayRepository<T> {
+
+ private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
+
+ init {
+ backgroundApplicationScope.launch("$debugName#start") { start() }
+ }
+
+ private suspend fun start() {
+ initCallback.onInit(debugName, this)
+ displayRepository.displayIds.collectLatest { displayIds ->
+ val toRemove = perDisplayInstances.keys - displayIds
+ toRemove.forEach { displayId ->
+ Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.")
+ perDisplayInstances.remove(displayId)?.let { instance ->
+ (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance(
+ instance
+ )
+ }
+ }
+ }
+ }
+
+ override fun get(displayId: Int): T? {
+ if (displayRepository.getDisplay(displayId) == null) {
+ Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.")
+ return null
+ }
+
+ // If it doesn't exist, create it and put it in the map.
+ return perDisplayInstances.computeIfAbsent(displayId) { key ->
+ Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.")
+ val instance =
+ traceSection({ "creating instance of $debugName for displayId=$key" }) {
+ instanceProvider.createInstance(key)
+ }
+ if (instance == null) {
+ Log.e(
+ TAG,
+ "<$debugName> returning null because createInstance($key) returned null.",
+ )
+ }
+ instance
+ }
+ }
+
+ @AssistedFactory
+ interface Factory<T> {
+ fun create(
+ debugName: String,
+ instanceProvider: PerDisplayInstanceProvider<T>,
+ ): PerDisplayInstanceRepositoryImpl<T>
+ }
+
+ companion object {
+ private const val TAG = "PerDisplayInstanceRepo"
+ }
+
+ override fun toString(): String {
+ return "PerDisplayInstanceRepositoryImpl(" +
+ "debugName='$debugName', instances=$perDisplayInstances)"
+ }
+}
+
+/**
+ * Provides an instance of a given class **only** for the default display, even if asked for another
+ * display.
+ *
+ * This is useful in case of **flag refactors**: it can be provided instead of an instance of
+ * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off.
+ *
+ * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If
+ * you want to provide an existing instance instead for the default display, either implement it in
+ * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the
+ * displayId is zero), or use [SingleInstanceRepositoryImpl].
+ */
+class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
+ override val debugName: String,
+ private val instanceProvider: PerDisplayInstanceProvider<T>,
+) : PerDisplayRepository<T> {
+ private val lazyDefaultDisplayInstance by lazy {
+ instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
+ }
+
+ override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
+}
+
+/**
+ * Always returns [instance] for any display.
+ *
+ * This can be used to provide a single instance based on a flag value during a refactor. Similar to
+ * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the
+ * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only,
+ * without even instantiating a [PerDisplayInstanceProvider].
+ */
+class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) :
+ PerDisplayRepository<T> {
+ override fun get(displayId: Int): T? = instance
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
index 564588c159bd..46048868f503 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
/** Provides per display instances of [T]. */
+@Deprecated("Use PerDisplayInstanceProvider<T> instead")
interface PerDisplayStore<T> {
/**
@@ -43,12 +44,13 @@ interface PerDisplayStore<T> {
fun forDisplay(displayId: Int): T?
}
+@Deprecated("Use PerDisplayRepository<T> instead")
abstract class PerDisplayStoreImpl<T>(
@Background private val backgroundApplicationScope: CoroutineScope,
private val displayRepository: DisplayRepository,
) : PerDisplayStore<T>, CoreStartable {
- private val perDisplayInstances = ConcurrentHashMap<Int, T>()
+ protected val perDisplayInstances = ConcurrentHashMap<Int, T>()
/**
* The instance for the default/main display of the device. For example, on a phone or a tablet,
@@ -106,6 +108,11 @@ abstract class PerDisplayStoreImpl<T>(
* Will be called when the display associated with [instance] was removed. It allows to perform
* any clean up if needed.
*/
+ @Deprecated(
+ "Use PerDisplayInstanceProviderWithTeardown instead, and let " +
+ "PerDisplayInstanceRepositoryImpl decide when to destroy the instance (e.g. on " +
+ "display removal or other conditions."
+ )
open suspend fun onDisplayRemovalAction(instance: T) {}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 15a3cbdb8072..84f103e8f730 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.display.domain.interactor
import android.companion.virtual.VirtualDeviceManager
import android.view.Display
+import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -138,7 +139,8 @@ constructor(
.distinctUntilChanged()
.flowOn(backgroundCoroutineDispatcher)
- private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
+ private fun DisplayRepositoryFromLib.PendingDisplay.toInteractorPendingDisplay():
+ PendingDisplay =
object : PendingDisplay {
override suspend fun enable() = this@toInteractorPendingDisplay.enable()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 12718e8bd119..9edd9dc056c7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -94,6 +94,7 @@ import java.util.function.Consumer;
public class DozeSensors {
private static final String TAG = "DozeSensors";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
+ private static final String KEY_DOZE_PULSE_ON_AUTH = "doze_pulse_on_auth";
private final AsyncSensorManager mSensorManager;
private final AmbientDisplayConfiguration mConfig;
@@ -241,7 +242,7 @@ public class DozeSensors {
),
new TriggerSensor(
findSensor(config.udfpsLongPressSensorType()),
- "doze_pulse_on_auth",
+ KEY_DOZE_PULSE_ON_AUTH,
true /* settingDef */,
udfpsLongPressConfigured(),
DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
@@ -421,6 +422,18 @@ public class DozeSensors {
&& (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
&& (!s.mRequiresProx || mListeningProxSensors)
&& (!s.mRequiresAod || mListeningAodOnlySensors);
+
+ //AOD might be turned off in visual because of BetterySaver or isAlwaysOnSuppressed(),
+ //but AOD isn't really turned off, in these cases, udfpsLongPressSensor should be
+ //unregistered.
+ if (!mListeningAodOnlySensors && KEY_DOZE_PULSE_ON_AUTH.equals(s.mSetting)) {
+ if (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId())
+ && !mConfig.screenOffUdfpsEnabled(
+ mSelectedUserInteractor.getSelectedUserId())) {
+ listen = false;
+ }
+ }
+
s.setListening(listen);
if (listen) {
anyListening = true;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
index d81949d08a66..c17094b7456c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -63,7 +63,7 @@ public class AssistantAttentionCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index c7fe1e1e754b..fb4ed1419688 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -63,7 +63,7 @@ public class DreamCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index a7c078f235b4..36b75c6fc6b8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -61,7 +61,7 @@ constructor(
fun startTransitionFromDream() {
val showGlanceableHub =
if (communalSettingsInteractor.isV2FlagEnabled()) {
- communalInteractor.shouldShowCommunal.value
+ communalSettingsInteractor.autoOpenEnabled.value
} else {
communalInteractor.isCommunalEnabled.value &&
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt
new file mode 100644
index 000000000000..438931a1962d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import com.android.systemui.Dumpable
+import java.io.PrintWriter
+
+/** Dumpable implementation that just calls toString() on the instance. */
+class DumpableFromToString<T>(private val instance: T) : Dumpable {
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("$instance")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
index dc08570447a5..e5920924a4be 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -16,7 +16,7 @@
package com.android.systemui.flags
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.statusbar.policy.BatteryController
import dagger.Lazy
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
index 4d89a826b60a..293461daef7b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -86,12 +86,13 @@ object RefactorFlagUtils {
* Example usage:
* ```
* public void setSomeNewController(SomeController someController) {
- * SomeRefactor.assertInNewMode();
+ * SomeRefactor.unsafeAssertInNewMode();
* mSomeController = someController;
* }
* ````
*/
- inline fun assertInNewMode(isEnabled: Boolean, flagName: Any) =
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode(isEnabled: Boolean, flagName: Any) =
check(isEnabled) { "New code path not supported when $flagName is disabled." }
/**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index cf5c3402792e..f2a10cc43fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -147,14 +147,14 @@ import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
+import dagger.Lazy;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
* on whether the keyguard is showing, and whether the device is provisioned.
@@ -270,6 +270,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final UserLogoutInteractor mLogoutInteractor;
private final GlobalActionsInteractor mInteractor;
private final Lazy<DisplayWindowPropertiesRepository> mDisplayWindowPropertiesRepositoryLazy;
+ private final Handler mHandler;
+
+ private final UserTracker.Callback mOnUserSwitched = new UserTracker.Callback() {
+ @Override
+ public void onBeforeUserSwitching(int newUser) {
+ // Dismiss the dialog as soon as we start switching. This will schedule a message
+ // in a handler so it will be pretty quick.
+ dismissDialog();
+ }
+ };
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -425,6 +435,29 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mInteractor = interactor;
mDisplayWindowPropertiesRepositoryLazy = displayWindowPropertiesRepository;
+ mHandler = new Handler(mMainHandler.getLooper()) {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
+ // Hide instantly.
+ mDialog.hide();
+ mDialog.dismiss();
+ } else {
+ mDialog.dismiss();
+ }
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ }
+ }
+ };
+
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -537,6 +570,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
expandable != null ? expandable.dialogTransitionController(
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG)) : null;
+ mUserTracker.addCallback(mOnUserSwitched, mBackgroundExecutor);
if (controller != null) {
mDialogTransitionAnimator.show(mDialog, controller);
} else {
@@ -1404,6 +1438,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mWindowManagerFuncs.onGlobalActionsHidden();
mLifecycle.setCurrentState(Lifecycle.State.CREATED);
mInteractor.onDismissed();
+ mUserTracker.removeCallback(mOnUserSwitched);
}
/**
@@ -2228,29 +2263,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mDialogPressDelay = 0; // ms
}
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_DISMISS:
- if (mDialog != null) {
- if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
- // Hide instantly.
- mDialog.hide();
- mDialog.dismiss();
- } else {
- mDialog.dismiss();
- }
- mDialog = null;
- }
- break;
- case MESSAGE_REFRESH:
- refreshSilentMode();
- mAdapter.notifyDataSetChanged();
- break;
- }
- }
- };
-
private void onAirplaneModeChanged() {
// Let the service state callbacks handle the state.
if (mHasTelephony || mAirplaneModeOn == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 54e27a61ac78..04a0630771df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -88,10 +88,7 @@ constructor(
private fun createDialog(): Dialog {
return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog
->
- val uiState by
- viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
- initialValue = ShortcutCustomizationUiState.Inactive
- )
+ val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle()
val coroutineScope = rememberCoroutineScope()
ShortcutCustomizationDialog(
uiState = uiState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 7e0fa2f125b0..c601e6e0baf6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -409,6 +409,7 @@ private fun Title(title: String) {
color = MaterialTheme.colorScheme.onSurface,
lineHeight = 32.sp,
fontWeight = FontWeight.W400,
+ textAlign = TextAlign.Center,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 922bc15c0633..4e7164ff12d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -21,7 +21,7 @@ import android.hardware.input.InputManager.StickyModifierStateListener
import android.hardware.input.StickyModifierState
import android.provider.Settings
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
index 9da9a7328659..32257df40d02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt
@@ -18,7 +18,7 @@ package com.android.systemui.keyevent.data.repository
import android.view.KeyEvent
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.CommandQueue
import javax.inject.Inject
@@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.Flow
interface KeyEventRepository {
/** Observable for whether the power button key is pressed/down or not. */
val isPowerButtonDown: Flow<Boolean>
+
+ /** Observable for when the power button is being pressed but till the duration of long press */
+ val isPowerButtonLongPressed: Flow<Boolean>
}
@SysUISingleton
@@ -51,6 +54,21 @@ constructor(
awaitClose { commandQueue.removeCallback(callback) }
}
+ override val isPowerButtonLongPressed: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : CommandQueue.Callbacks {
+ override fun handleSystemKey(event: KeyEvent) {
+ if (event.keyCode == KeyEvent.KEYCODE_POWER) {
+ trySendWithFailureLogging(event.action == KeyEvent.ACTION_DOWN
+ && event.isLongPress, TAG, "updated isPowerButtonLongPressed")
+ }
+ }
+ }
+ trySendWithFailureLogging(false, TAG, "init isPowerButtonLongPressed")
+ commandQueue.addCallback(callback)
+ awaitClose { commandQueue.removeCallback(callback) }
+ }
+
companion object {
private const val TAG = "KeyEventRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
index 9949fa589cd5..ec9bbfb1bc77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -32,4 +32,5 @@ constructor(
repository: KeyEventRepository,
) {
val isPowerButtonDown = repository.isPowerButtonDown
+ val isPowerButtonLongPressed = repository.isPowerButtonLongPressed
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
index 1febc79b8241..bc65ad476c37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
@@ -42,7 +42,7 @@ constructor(
when (event.keyCode) {
KeyEvent.KEYCODE_BACK -> {
- if (event.handleAction()) {
+ if (!backActionInteractor.isBackCallbackRegistered() && event.handleAction()) {
backActionInteractor.onBackRequested()
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 757464976261..79685088fed7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -673,7 +673,8 @@ public class KeyguardService extends Service {
if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractorLazy.get().lockNow("doKeyguardTimeout");
} else if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardServiceShowLockscreenInteractor.onKeyguardServiceDoKeyguardTimeout();
+ mKeyguardServiceShowLockscreenInteractor
+ .onKeyguardServiceDoKeyguardTimeout(options);
}
mKeyguardViewMediator.doKeyguardTimeout(options);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 3b85b571023f..9ade5036b9a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -51,10 +51,10 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b4b3053cba42..d8fc21af9724 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1847,6 +1847,7 @@ public class KeyguardViewMediator implements CoreStartable,
// explicitly DO NOT want to call
// mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
// here, since that will mess with the device lock state.
+ mKeyguardStateController.notifyKeyguardGoingAway(false);
mUpdateMonitor.dispatchKeyguardGoingAway(false);
notifyStartedGoingToSleep();
@@ -2994,7 +2995,6 @@ public class KeyguardViewMediator implements CoreStartable,
startKeyguardTransition(showing, aodShowing);
} else {
try {
-
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
} catch (RemoteException ignored) {
}
@@ -3650,30 +3650,33 @@ public class KeyguardViewMediator implements CoreStartable,
return;
}
- try {
- int flags = KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS
- | KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+ int flags = KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS
+ | KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
- // If we are unlocking to the launcher, clear the snapshot so that any changes as part
- // of the in-window animations are reflected. This is needed even if we're not actually
- // playing in-window animations for this particular unlock since a previous unlock might
- // have changed the Launcher state.
- if (mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath()) {
- flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
- }
+ // If we are unlocking to the launcher, clear the snapshot so that any changes as part
+ // of the in-window animations are reflected. This is needed even if we're not actually
+ // playing in-window animations for this particular unlock since a previous unlock might
+ // have changed the Launcher state.
+ if (mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath()) {
+ flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+ }
- mKeyguardStateController.notifyKeyguardGoingAway(true);
+ mKeyguardStateController.notifyKeyguardGoingAway(true);
- if (!KeyguardWmStateRefactor.isEnabled()) {
- // Handled in WmLockscreenVisibilityManager.
- mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ // Handled in WmLockscreenVisibilityManager.
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ final int goingAwayFlags = flags;
+ mUiBgExecutor.execute(() -> {
Log.d(TAG, "keyguardGoingAway requested for userId: "
+ mGoingAwayRequestedForUserId);
- mActivityTaskManagerService.keyguardGoingAway(flags);
- }
- } catch (RemoteException e) {
- mSurfaceBehindRemoteAnimationRequested = false;
- Log.e(TAG, "Failed to report keyguardGoingAway", e);
+ try {
+ mActivityTaskManagerService.keyguardGoingAway(goingAwayFlags);
+ } catch (RemoteException e) {
+ mSurfaceBehindRemoteAnimationRequested = false;
+ Log.e(TAG, "Failed to report keyguardGoingAway", e);
+ }
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 58692746d1e0..51b953ef290c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -22,11 +22,14 @@ import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationTarget
import android.view.WindowManager
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardShowWhileAwakeInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.window.flags.Flags
import com.android.wm.shell.keyguard.KeyguardTransitions
import java.util.concurrent.Executor
@@ -46,6 +49,9 @@ constructor(
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
private val keyguardTransitions: KeyguardTransitions,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardShowWhileAwakeInteractor: KeyguardShowWhileAwakeInteractor,
) {
/**
@@ -92,12 +98,23 @@ constructor(
* second timeout).
*/
private var isKeyguardGoingAway = false
- private set(value) {
+ private set(goingAway) {
// TODO(b/278086361): Extricate the keyguard state controller.
- keyguardStateController.notifyKeyguardGoingAway(value)
- field = value
+ keyguardStateController.notifyKeyguardGoingAway(goingAway)
+
+ if (goingAway) {
+ keyguardGoingAwayRequestedForUserId = selectedUserInteractor.getSelectedUserId()
+ }
+
+ field = goingAway
}
+ /**
+ * The current user ID when we asked WM to start the keyguard going away animation. This is used
+ * for validation when user switching occurs during unlock.
+ */
+ private var keyguardGoingAwayRequestedForUserId: Int = -1
+
/** Callback provided by WM to call once we're done with the going away animation. */
private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
@@ -171,6 +188,14 @@ constructor(
nonApps: Array<RemoteAnimationTarget>,
finishedCallback: IRemoteAnimationFinishedCallback,
) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+
+ if (maybeStartTransitionIfUserSwitchedDuringGoingAway()) {
+ Log.d(TAG, "User switched during keyguard going away - ending remote animation.")
+ endKeyguardGoingAwayAnimation()
+ return
+ }
+
// If we weren't expecting the keyguard to be going away, WM triggered this transition.
if (!isKeyguardGoingAway) {
// Since WM triggered this, we're likely not transitioning to GONE yet. See if we can
@@ -198,7 +223,6 @@ constructor(
}
if (apps.isNotEmpty()) {
- goingAwayRemoteAnimationFinishedCallback = finishedCallback
keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
} else {
// Nothing to do here if we have no apps, end the animation, which will cancel it and WM
@@ -211,6 +235,7 @@ constructor(
// If WM cancelled the animation, we need to end immediately even if we're still using the
// animation.
endKeyguardGoingAwayAnimation()
+ maybeStartTransitionIfUserSwitchedDuringGoingAway()
}
/**
@@ -301,6 +326,29 @@ constructor(
}
}
+ /**
+ * If necessary, start a transition to show/hide keyguard in response to a user switch during
+ * keyguard going away.
+ *
+ * Returns [true] if a transition was started, or false if a transition was not necessary.
+ */
+ private fun maybeStartTransitionIfUserSwitchedDuringGoingAway(): Boolean {
+ val currentUser = selectedUserInteractor.getSelectedUserId()
+ if (currentUser != keyguardGoingAwayRequestedForUserId) {
+ if (lockPatternUtils.isSecure(currentUser)) {
+ keyguardShowWhileAwakeInteractor.onSwitchedToSecureUserWhileKeyguardGoingAway()
+ } else {
+ keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
+ reason = "User switch during keyguard going away, and new user is insecure"
+ )
+ }
+
+ return true
+ } else {
+ return false
+ }
+ }
+
companion object {
private val TAG = "WindowManagerLsVis"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 2c5bacb62e72..70e2413386e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -196,6 +196,9 @@ public class WorkLockActivity extends Activity {
confirmCredentialIntent.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
}
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ confirmCredentialIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+
// WorkLockActivity is started as a task overlay, so unless credential confirmation is also
// started as an overlay, it won't be visible.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index f11ebee46659..40861929add7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -30,7 +30,7 @@ import com.android.settingslib.notification.modes.EnableDndDialogFactory
import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
@@ -98,7 +98,7 @@ class DoNotDisturbQuickAffordanceConfig(
private var settingsValue: Int = 0
private val isAvailable: StateFlow<Boolean> by lazy {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
interactor.isZenAvailable.stateIn(
scope = backgroundScope,
started = SharingStarted.Eagerly,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
index e2642a0964c1..683c11a88b89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -20,7 +20,7 @@ package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 3555f06ce96f..a1dafb1438ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -24,7 +24,7 @@ import androidx.annotation.DrawableRes
import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.controls.ControlsServiceInfo
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
index ad79177fdd76..01ff0e1344c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -23,7 +23,7 @@ import android.content.SharedPreferences
import com.android.systemui.backup.BackupHelper
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index 1c9bc9f39663..10fc4c2a02ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d12c42a754f0..7c33e29bf25a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,7 @@ import android.content.Context
import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 760adbf58d93..56ea26e88b23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -26,7 +26,7 @@ import android.service.quickaccesswallet.WalletCard
import android.util.Log
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 0f5f31302670..30476b991baf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -35,7 +35,7 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 4d999df69588..396f60645f00 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -24,7 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
index 7c430920cb46..59e6a08c4511 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.DevicePosture
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 acb98ede3e80..cd0efdae337d 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
@@ -24,7 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -662,12 +662,12 @@ constructor(
}
override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
}
override fun isShowKeyguardWhenReenabled(): Boolean {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
return isShowKeyguardWhenReenabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt
new file mode 100644
index 000000000000..16c2d14b78ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os.IRemoteCallback
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Holds an IRemoteCallback along with the current user ID at the time the callback was provided.
+ */
+data class ShowLockscreenCallback(val userId: Int, val remoteCallback: IRemoteCallback)
+
+/** Maintains state related to KeyguardService requests to show the lockscreen. */
+@SysUISingleton
+class KeyguardServiceShowLockscreenRepository @Inject constructor() {
+ val showLockscreenCallbacks = ArrayList<ShowLockscreenCallback>()
+
+ /**
+ * Adds a callback that we'll notify when we show the lockscreen (or affirmatively decide not to
+ * show it).
+ */
+ fun addShowLockscreenCallback(forUser: Int, callback: IRemoteCallback) {
+ synchronized(showLockscreenCallbacks) {
+ showLockscreenCallbacks.add(ShowLockscreenCallback(forUser, callback))
+ }
+ }
+
+ companion object {
+ private const val TAG = "ShowLockscreenRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index c5a6fa199c58..63a0286832d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -20,7 +20,7 @@ import android.app.trust.TrustManager
import com.android.keyguard.TrustGrantFlags
import com.android.keyguard.logging.TrustRepositoryLogger
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index ef06a85bd0d9..f53421d539fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -20,7 +20,6 @@ import android.animation.ValueAnimator
import android.util.Log
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,7 +59,6 @@ constructor(
private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
- private val communalInteractor: CommunalInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
@@ -103,14 +101,14 @@ constructor(
)
.collect {
(
- _,
+ detailedWakefulness,
startedStep,
canWakeDirectlyToGone,
) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
- val shouldShowCommunal = communalInteractor.shouldShowCommunal.value
+ val autoOpenCommunal = communalSettingsInteractor.autoOpenEnabled.value
if (!maybeHandleInsecurePowerGesture()) {
val shouldTransitionToLockscreen =
@@ -137,8 +135,12 @@ constructor(
(!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen()) ||
(KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
+ // Avoid transitioning to communal automatically if the device is waking
+ // up due to motion.
val shouldTransitionToCommunal =
- communalSettingsInteractor.isV2FlagEnabled() && shouldShowCommunal
+ communalSettingsInteractor.isV2FlagEnabled() &&
+ autoOpenCommunal &&
+ !detailedWakefulness.isAwakeFromMotionOrLift()
if (shouldTransitionToGone) {
// TODO(b/360368320): Adapt for scene framework
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 6f5f662d6fa3..4aaa1fab4c65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -34,8 +34,9 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -121,9 +122,10 @@ constructor(
private fun shouldTransitionToCommunal(
shouldShowCommunal: Boolean,
isCommunalAvailable: Boolean,
+ wakefulness: WakefulnessModel,
) =
if (communalSettingsInteractor.isV2FlagEnabled()) {
- shouldShowCommunal
+ shouldShowCommunal && !wakefulness.isAwakeFromMotionOrLift()
} else {
isCommunalAvailable && dreamManager.canStartDreaming(false)
}
@@ -148,14 +150,14 @@ constructor(
}
scope.launch {
- powerInteractor.isAwake
+ powerInteractor.detailedWakefulness
.debounce(50L)
- .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(
+ .filterRelevantKeyguardStateAnd { wakefulness -> wakefulness.isAwake() }
+ .sampleCombine(
communalInteractor.isCommunalAvailable,
- communalInteractor.shouldShowCommunal,
+ communalSettingsInteractor.autoOpenEnabled,
)
- .collect { (_, isCommunalAvailable, shouldShowCommunal) ->
+ .collect { (detailedWakefulness, isCommunalAvailable, shouldShowCommunal) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
@@ -186,7 +188,11 @@ constructor(
} else if (isKeyguardOccludedLegacy) {
startTransitionTo(KeyguardState.OCCLUDED)
} else if (
- shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ shouldTransitionToCommunal(
+ shouldShowCommunal,
+ isCommunalAvailable,
+ detailedWakefulness,
+ )
) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
@@ -208,8 +214,8 @@ constructor(
scope.launch {
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
- .sample(
- communalInteractor.shouldShowCommunal,
+ .sampleCombine(
+ communalSettingsInteractor.autoOpenEnabled,
communalInteractor.isCommunalAvailable,
keyguardInteractor.biometricUnlockState,
wakeToGoneInteractor.canWakeDirectlyToGone,
@@ -217,7 +223,7 @@ constructor(
)
.collect {
(
- _,
+ detailedWakefulness,
shouldShowCommunal,
isCommunalAvailable,
biometricUnlockState,
@@ -245,7 +251,11 @@ constructor(
)
}
} else if (
- shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ shouldTransitionToCommunal(
+ shouldShowCommunal,
+ isCommunalAvailable,
+ detailedWakefulness,
+ )
) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0fb98ffa4a30..3b1b6fcc45f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -115,7 +115,7 @@ constructor(
powerInteractor.isAwake
.debounce(50L)
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(communalInteractor.shouldShowCommunal)
+ .sample(communalSettingsInteractor.autoOpenEnabled)
.collect { shouldShowCommunal ->
if (shouldShowCommunal) {
// This case handles tapping the power button to transition through
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index a01dc02bbd9f..f8c7a86687dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,7 +20,6 @@ import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -69,7 +68,6 @@ constructor(
private val shadeRepository: ShadeRepository,
powerInteractor: PowerInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
- private val communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
private val swipeToDismissInteractor: SwipeToDismissInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -355,7 +353,7 @@ constructor(
private fun listenForLockscreenToGlanceableHubV2() {
scope.launch {
- communalInteractor.shouldShowCommunal
+ communalSettingsInteractor.autoOpenEnabled
.filterRelevantKeyguardStateAnd { shouldShow -> shouldShow }
.collect {
communalSceneInteractor.changeScene(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index ab0efed2cb76..02e04aa279d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -226,6 +226,10 @@ constructor(
}
fun handleFidgetTap(x: Float, y: Float) {
+ if (!com.android.systemui.Flags.clockFidgetAnimation()) {
+ return
+ }
+
if (selectedClockSize.value == ClockSizeSetting.DYNAMIC) {
clockEventController.handleFidgetTap(x, y)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
index b55bb383c308..07a31e16384c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
@@ -17,16 +17,27 @@
package com.android.systemui.keyguard.domain.interactor
import android.annotation.SuppressLint
+import android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK
+import android.os.Bundle
+import android.os.IRemoteCallback
+import android.os.RemoteException
+import android.util.Log
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardServiceShowLockscreenRepository
+import com.android.systemui.keyguard.data.repository.ShowLockscreenCallback
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
/**
- * Logic around requests by [KeyguardService] to show keyguard right now, even though the device is
- * awake and not going to sleep.
+ * Logic around requests by [KeyguardService] and friends to show keyguard right now, even though
+ * the device is awake and not going to sleep.
*
* This can happen if WM#lockNow() is called, if KeyguardService#showDismissibleKeyguard is called
* because we're folding with "continue using apps on fold" set to "swipe up to continue", or if the
@@ -38,7 +49,28 @@ import kotlinx.coroutines.launch
@SysUISingleton
class KeyguardServiceShowLockscreenInteractor
@Inject
-constructor(@Background val backgroundScope: CoroutineScope) {
+constructor(
+ @Background val backgroundScope: CoroutineScope,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val repository: KeyguardServiceShowLockscreenRepository,
+ private val userTracker: UserTracker,
+ private val wmLockscreenVisibilityInteractor: Lazy<WindowManagerLockscreenVisibilityInteractor>,
+ private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
+) : CoreStartable {
+
+ override fun start() {
+ backgroundScope.launch {
+ // Whenever we tell ATMS that lockscreen is visible, notify any showLockscreenCallbacks.
+ // This is not the only place we notify the lockNowCallbacks - there are cases where we
+ // decide not to show the lockscreen despite being asked to, and we need to notify the
+ // callback in those cases as well.
+ wmLockscreenVisibilityInteractor.get().lockscreenVisibility.collect { visible ->
+ if (visible) {
+ notifyShowLockscreenCallbacks()
+ }
+ }
+ }
+ }
/**
* Emits whenever [KeyguardService] receives a call that indicates we should show the lockscreen
@@ -57,9 +89,38 @@ constructor(@Background val backgroundScope: CoroutineScope) {
/**
* Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
* the device locked while the screen was on.
+ *
+ * We'll show keyguard, and if provided, save the lock on user switch callback, to notify it
+ * later when we successfully show.
*/
- fun onKeyguardServiceDoKeyguardTimeout() {
+ fun onKeyguardServiceDoKeyguardTimeout(options: Bundle? = null) {
backgroundScope.launch {
+ if (options?.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) {
+ val userId = userTracker.userId
+
+ // This callback needs to be invoked after we show the lockscreen (or decide not to
+ // show it) otherwise System UI will crash in 20 seconds, as a security measure.
+ repository.addShowLockscreenCallback(
+ userId,
+ IRemoteCallback.Stub.asInterface(
+ options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK)
+ ),
+ )
+
+ Log.d(
+ TAG,
+ "Showing lockscreen now - setting required callback for user $userId. " +
+ "SysUI will crash if this callback is not invoked.",
+ )
+
+ // If the keyguard is disabled or suppressed, we'll never actually show the
+ // lockscreen. Notify the callback so we don't crash.
+ if (!keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed()) {
+ Log.d(TAG, "Keyguard is disabled or suppressed, notifying callbacks now.")
+ notifyShowLockscreenCallbacks()
+ }
+ }
+
showNowEvents.emit(ShowWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON)
}
}
@@ -74,4 +135,33 @@ constructor(@Background val backgroundScope: CoroutineScope) {
showNowEvents.emit(ShowWhileAwakeReason.FOLDED_WITH_SWIPE_UP_TO_CONTINUE)
}
}
+
+ /** Notifies the callbacks that we've either locked, or decided not to lock. */
+ private fun notifyShowLockscreenCallbacks() {
+ var callbacks: MutableList<ShowLockscreenCallback>
+ synchronized(repository.showLockscreenCallbacks) {
+ callbacks = ArrayList(repository.showLockscreenCallbacks)
+ repository.showLockscreenCallbacks.clear()
+ }
+
+ val iter: MutableIterator<ShowLockscreenCallback> = callbacks.listIterator()
+ while (iter.hasNext()) {
+ val callback = iter.next()
+ iter.remove()
+ if (callback.userId != selectedUserInteractor.getSelectedUserId()) {
+ Log.i(TAG, "Not notifying lockNowCallback due to user mismatch")
+ continue
+ }
+ Log.i(TAG, "Notifying lockNowCallback")
+ try {
+ callback.remoteCallback.sendResult(null)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Could not issue LockNowCallback sendResult", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "ShowLockscreenInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt
index a8000a568a6a..c67939a0738a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractor.kt
@@ -16,15 +16,20 @@
package com.android.systemui.keyguard.domain.interactor
+import android.annotation.SuppressLint
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/** The reason we're showing lockscreen while awake, used for logging. */
enum class ShowWhileAwakeReason(private val logReason: String) {
@@ -38,6 +43,9 @@ enum class ShowWhileAwakeReason(private val logReason: String) {
),
KEYGUARD_TIMEOUT_WHILE_SCREEN_ON(
"Timed out while the screen was kept on, or WM#lockNow() was called."
+ ),
+ SWITCHED_TO_SECURE_USER_WHILE_GOING_AWAY(
+ "User switch to secure user occurred during keyguardGoingAway sequence, so we're locking."
);
override fun toString(): String {
@@ -68,6 +76,7 @@ enum class ShowWhileAwakeReason(private val logReason: String) {
class KeyguardShowWhileAwakeInteractor
@Inject
constructor(
+ @Background val backgroundScope: CoroutineScope,
biometricSettingsRepository: BiometricSettingsRepository,
keyguardEnabledInteractor: KeyguardEnabledInteractor,
keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor,
@@ -91,6 +100,15 @@ constructor(
.filter { reshow -> reshow }
.map { ShowWhileAwakeReason.KEYGUARD_REENABLED }
+ /**
+ * Emits whenever a user switch to a secure user occurs during keyguard going away.
+ *
+ * This is an event flow, hence the SharedFlow.
+ */
+ @SuppressLint("SharedFlowCreation")
+ val switchedToSecureUserDuringGoingAway: MutableSharedFlow<ShowWhileAwakeReason> =
+ MutableSharedFlow()
+
/** Emits whenever we should show lockscreen while the screen is on, for any reason. */
val showWhileAwakeEvents: Flow<ShowWhileAwakeReason> =
merge(
@@ -108,5 +126,15 @@ constructor(
keyguardServiceShowLockscreenInteractor.showNowEvents.filter {
keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed()
},
+ switchedToSecureUserDuringGoingAway,
)
+
+ /** A user switch to a secure user occurred while we were going away. We need to re-lock. */
+ fun onSwitchedToSecureUserWhileKeyguardGoingAway() {
+ backgroundScope.launch {
+ switchedToSecureUserDuringGoingAway.emit(
+ ShowWhileAwakeReason.SWITCHED_TO_SECURE_USER_WHILE_GOING_AWAY
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index 705eaa22aa9a..55534c4f1444 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -20,11 +20,14 @@ package com.android.systemui.keyguard.domain.interactor
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.os.PowerManager
+import android.provider.Settings
import android.view.accessibility.AccessibilityManager
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags.doubleTapToSleep
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -36,10 +39,13 @@ import com.android.systemui.res.R
import com.android.systemui.shade.PulsingGestureListener
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -66,10 +72,13 @@ constructor(
private val accessibilityManager: AccessibilityManagerWrapper,
private val pulsingGestureListener: PulsingGestureListener,
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val secureSettingsRepository: UserAwareSecureSettingsRepository,
+ private val powerManager: PowerManager,
+ private val systemClock: SystemClock,
) {
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: StateFlow<Boolean> =
- if (isFeatureEnabled()) {
+ if (isLongPressFeatureEnabled()) {
combine(
transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
repository.isQuickSettingsVisible,
@@ -85,6 +94,30 @@ constructor(
initialValue = false,
)
+ /** Whether the double tap handling handling feature should be enabled. */
+ val isDoubleTapHandlingEnabled: StateFlow<Boolean> =
+ if (isDoubleTapFeatureEnabled()) {
+ combine(
+ transitionInteractor.transitionValue(KeyguardState.LOCKSCREEN),
+ repository.isQuickSettingsVisible,
+ isDoubleTapSettingEnabled(),
+ ) {
+ isFullyTransitionedToLockScreen,
+ isQuickSettingsVisible,
+ isDoubleTapSettingEnabled ->
+ isFullyTransitionedToLockScreen == 1f &&
+ !isQuickSettingsVisible &&
+ isDoubleTapSettingEnabled
+ }
+ } else {
+ flowOf(false)
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
private val _isMenuVisible = MutableStateFlow(false)
/** Model for whether the menu should be shown. */
val isMenuVisible: StateFlow<Boolean> =
@@ -116,7 +149,7 @@ constructor(
private var delayedHideMenuJob: Job? = null
init {
- if (isFeatureEnabled()) {
+ if (isLongPressFeatureEnabled()) {
broadcastDispatcher
.broadcastFlow(IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
.onEach { hideMenu() }
@@ -175,17 +208,30 @@ constructor(
/** Notifies that the lockscreen has been double clicked. */
fun onDoubleClick() {
- pulsingGestureListener.onDoubleTapEvent()
+ if (isDoubleTapHandlingEnabled.value) {
+ powerManager.goToSleep(systemClock.uptimeMillis())
+ } else {
+ pulsingGestureListener.onDoubleTapEvent()
+ }
+ }
+
+ private fun isDoubleTapSettingEnabled(): Flow<Boolean> {
+ return secureSettingsRepository.boolSetting(Settings.Secure.DOUBLE_TAP_TO_SLEEP)
}
private fun showSettings() {
_shouldOpenSettings.value = true
}
- private fun isFeatureEnabled(): Boolean {
+ private fun isLongPressFeatureEnabled(): Boolean {
return context.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled)
}
+ private fun isDoubleTapFeatureEnabled(): Boolean {
+ return doubleTapToSleep() &&
+ context.resources.getBoolean(com.android.internal.R.bool.config_supportDoubleTapSleep)
+ }
+
/** Updates application state to ask to show the menu. */
private fun showMenu() {
_isMenuVisible.value = true
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
index 2b4582a2a096..780b7fb06b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -29,6 +29,7 @@ constructor(
private val auditLogger: KeyguardTransitionAuditLogger,
private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor,
private val keyguardStateCallbackInteractor: KeyguardStateCallbackInteractor,
+ private val keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor,
) : CoreStartable {
override fun start() {
@@ -54,6 +55,7 @@ constructor(
auditLogger.start()
statusBarDisableFlagsInteractor.start()
keyguardStateCallbackInteractor.start()
+ keyguardServiceShowLockscreenInteractor.start()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
index 0b587ae1f58e..c031b53ab87d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -38,9 +38,15 @@ object AlternateBouncerUdfpsViewBinder {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
view.alpha = 0f
+
launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
+ if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+ view.setOnClickListener { viewModel.onTapped() }
+ } else {
+ view.setOnClickListener(null)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 17e14c3e83da..70a827d5e45b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -48,7 +48,7 @@ object DeviceEntryIconViewBinder {
/**
* Updates UI for:
* - device entry containing view (parent view for the below views)
- * - long-press handling view (transparent, no UI)
+ * - touch handling view (transparent, no UI)
* - foreground icon view (lock/unlock/fingerprint)
* - background view (optional)
*/
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
index 741b149f29da..92b9da6790d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -31,6 +31,37 @@ import com.android.systemui.plugins.clocks.ClockPreviewConfig
object KeyguardPreviewSmartspaceViewBinder {
@JvmStatic
+ fun bind(parentView: View, viewModel: KeyguardPreviewSmartspaceViewModel) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ val largeDateView =
+ parentView.findViewById<View>(
+ com.android.systemui.shared.R.id.date_smartspace_view_large
+ )
+ val smallDateView =
+ parentView.findViewById<View>(com.android.systemui.shared.R.id.date_smartspace_view)
+ parentView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch("$TAG#viewModel.selectedClockSize") {
+ viewModel.previewingClockSize.collect {
+ when (it) {
+ ClockSizeSetting.DYNAMIC -> {
+ smallDateView?.visibility = View.GONE
+ largeDateView?.visibility = View.VISIBLE
+ }
+
+ ClockSizeSetting.SMALL -> {
+ smallDateView?.visibility = View.VISIBLE
+ largeDateView?.visibility = View.GONE
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @JvmStatic
fun bind(
smartspace: View,
viewModel: KeyguardPreviewSmartspaceViewModel,
@@ -44,6 +75,7 @@ object KeyguardPreviewSmartspaceViewBinder {
when (it) {
ClockSizeSetting.DYNAMIC ->
viewModel.getLargeClockSmartspaceTopPadding(clockPreviewConfig)
+
ClockSizeSetting.SMALL ->
viewModel.getSmallClockSmartspaceTopPadding(clockPreviewConfig)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 45801ba3517a..aeb327035c79 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -333,7 +333,7 @@ object KeyguardRootViewBinder {
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
- deviceEntryHapticsInteractor.playSuccessHaptic.collect {
+ deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry.collect {
if (msdlFeedback()) {
msdlPlayer?.playToken(
MSDLToken.UNLOCK,
@@ -474,7 +474,7 @@ object KeyguardRootViewBinder {
val transition = blueprintViewModel.currentTransition.value
val shouldAnimate = transition != null && transition.config.type.animateNotifChanges
if (prevTransition == transition && shouldAnimate) {
- logger.w("Skipping; layout during transition")
+ logger.w("Skipping onNotificationContainerBoundsChanged during transition")
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index e81d5354ec6e..5ef2d6fd3256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.clocks.VRectF
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
import kotlinx.coroutines.DisposableHandle
@@ -135,7 +136,7 @@ object KeyguardSmartspaceViewBinder {
}
}
- if (clockBounds == null) return@collect
+ if (clockBounds == VRectF.ZERO) return@collect
if (isLargeClock) {
val largeDateHeight =
keyguardRootView
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt
index 195413a80f4b..485e1ce5b2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt
@@ -75,6 +75,13 @@ object KeyguardTouchViewBinder {
onSingleTap(x, y)
}
+
+ override fun onDoubleTapDetected(view: View) {
+ if (falsingManager.isFalseDoubleTap()) {
+ return
+ }
+ viewModel.onDoubleClick()
+ }
}
view.repeatWhenAttached {
@@ -90,9 +97,20 @@ object KeyguardTouchViewBinder {
}
}
}
+ launch("$TAG#viewModel.isDoubleTapHandlingEnabled") {
+ viewModel.isDoubleTapHandlingEnabled.collect { isEnabled ->
+ view.setDoublePressHandlingEnabled(isEnabled)
+ view.contentDescription =
+ if (isEnabled) {
+ view.resources.getString(R.string.accessibility_desc_lock_screen)
+ } else {
+ null
+ }
+ }
+ }
}
}
}
- private const val TAG = "KeyguardLongPressViewBinder"
+ private const val TAG = "KeyguardTouchViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 242926b3e1d1..d749e3c11378 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -68,6 +68,7 @@ import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockPreviewConfig
+import com.android.systemui.plugins.clocks.ContextExt.getId
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.res.R
@@ -126,6 +127,7 @@ constructor(
private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY)
private val display: Display? = displayManager.getDisplay(displayId)
+
/**
* Returns a key that should make the KeyguardPreviewRenderer unique and if two of them have the
* same key they will be treated as the same KeyguardPreviewRenderer. Primary this is used to
@@ -144,6 +146,8 @@ constructor(
get() = checkNotNull(host.surfacePackage)
private var smartSpaceView: View? = null
+ private var largeDateView: View? = null
+ private var smallDateView: View? = null
private val disposables = DisposableHandles()
private var isDestroyed = false
@@ -181,7 +185,7 @@ constructor(
ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
} ?: context
- val rootView = FrameLayout(previewContext)
+ val rootView = ConstraintLayout(previewContext)
setupKeyguardRootView(previewContext, rootView)
@@ -252,6 +256,24 @@ constructor(
fun onClockSizeSelected(clockSize: ClockSizeSetting) {
smartspaceViewModel.setOverrideClockSize(clockSize)
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ when (clockSize) {
+ ClockSizeSetting.DYNAMIC -> {
+ largeDateView?.post {
+ smallDateView?.visibility = View.GONE
+ largeDateView?.visibility = View.VISIBLE
+ }
+ }
+
+ ClockSizeSetting.SMALL -> {
+ largeDateView?.post {
+ smallDateView?.visibility = View.VISIBLE
+ largeDateView?.visibility = View.GONE
+ }
+ }
+ }
+ smartSpaceView?.post { smartSpaceView?.visibility = View.GONE }
+ }
}
fun destroy() {
@@ -280,7 +302,7 @@ constructor(
*
* The end padding is as follows: Below clock padding end
*/
- private fun setUpSmartspace(previewContext: Context, parentView: ViewGroup) {
+ private fun setUpSmartspace(previewContext: Context, parentView: ConstraintLayout) {
if (
!lockscreenSmartspaceController.isEnabled ||
!lockscreenSmartspaceController.isDateWeatherDecoupled
@@ -292,40 +314,90 @@ constructor(
parentView.removeView(smartSpaceView)
}
- smartSpaceView =
- lockscreenSmartspaceController.buildAndConnectDateView(
- parent = parentView,
- isLargeClock = false,
- )
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ val cs = ConstraintSet()
+ cs.clone(parentView)
+ cs.apply {
+ val largeClockViewId = previewContext.getId("lockscreen_clock_view_large")
+ val smallClockViewId = previewContext.getId("lockscreen_clock_view")
+ largeDateView =
+ lockscreenSmartspaceController
+ .buildAndConnectDateView(parentView, true)
+ ?.also { view ->
+ constrainWidth(view.id, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(view.id, ConstraintSet.WRAP_CONTENT)
+ connect(view.id, START, largeClockViewId, START)
+ connect(view.id, ConstraintSet.END, largeClockViewId, ConstraintSet.END)
+ connect(
+ view.id,
+ TOP,
+ largeClockViewId,
+ ConstraintSet.BOTTOM,
+ smartspaceViewModel.getDateWeatherEndPadding(previewContext),
+ )
+ }
+ smallDateView =
+ lockscreenSmartspaceController
+ .buildAndConnectDateView(parentView, false)
+ ?.also { view ->
+ constrainWidth(view.id, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(view.id, ConstraintSet.WRAP_CONTENT)
+ connect(
+ view.id,
+ START,
+ smallClockViewId,
+ ConstraintSet.END,
+ context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_horizontal
+ ),
+ )
+ connect(view.id, TOP, smallClockViewId, TOP)
+ connect(
+ view.id,
+ ConstraintSet.BOTTOM,
+ smallClockViewId,
+ ConstraintSet.BOTTOM,
+ )
+ }
+ parentView.addView(largeDateView)
+ parentView.addView(smallDateView)
+ }
+ cs.applyTo(parentView)
+ } else {
+ smartSpaceView =
+ lockscreenSmartspaceController.buildAndConnectDateView(
+ parent = parentView,
+ isLargeClock = false,
+ )
- val topPadding: Int =
- smartspaceViewModel.getLargeClockSmartspaceTopPadding(
- ClockPreviewConfig(
- previewContext,
- getPreviewShadeLayoutWide(display!!),
- SceneContainerFlag.isEnabled,
+ val topPadding: Int =
+ smartspaceViewModel.getLargeClockSmartspaceTopPadding(
+ ClockPreviewConfig(
+ previewContext,
+ getPreviewShadeLayoutWide(display!!),
+ SceneContainerFlag.isEnabled,
+ )
)
- )
- val startPadding: Int = smartspaceViewModel.getDateWeatherStartPadding(previewContext)
- val endPadding: Int = smartspaceViewModel.getDateWeatherEndPadding(previewContext)
-
- smartSpaceView?.let {
- it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
- it.isClickable = false
- it.isInvisible = true
- parentView.addView(
- it,
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT,
- ),
- )
+ val startPadding: Int = smartspaceViewModel.getDateWeatherStartPadding(previewContext)
+ val endPadding: Int = smartspaceViewModel.getDateWeatherEndPadding(previewContext)
+
+ smartSpaceView?.let {
+ it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
+ it.isClickable = false
+ it.isInvisible = true
+ parentView.addView(
+ it,
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ ),
+ )
+ }
+ smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
}
-
- smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
}
- private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
+ private fun setupKeyguardRootView(previewContext: Context, rootView: ConstraintLayout) {
val keyguardRootView = KeyguardRootView(previewContext, null)
rootView.addView(
keyguardRootView,
@@ -341,6 +413,13 @@ constructor(
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ setUpSmartspace(previewContext, keyguardRootView)
+ KeyguardPreviewSmartspaceViewBinder.bind(
+ keyguardRootView,
+ smartspaceViewModel,
+ )
+ }
KeyguardPreviewClockViewBinder.bind(
keyguardRootView,
clockViewModel,
@@ -354,19 +433,22 @@ constructor(
)
}
- setUpSmartspace(previewContext, rootView)
-
- smartSpaceView?.let {
- KeyguardPreviewSmartspaceViewBinder.bind(
- it,
- smartspaceViewModel,
- clockPreviewConfig =
- ClockPreviewConfig(
- previewContext,
- getPreviewShadeLayoutWide(display!!),
- SceneContainerFlag.isEnabled,
- ),
- )
+ if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ setUpSmartspace(previewContext, keyguardRootView)
+ smartSpaceView?.let {
+ KeyguardPreviewSmartspaceViewBinder.bind(
+ it,
+ smartspaceViewModel,
+ clockPreviewConfig =
+ ClockPreviewConfig(
+ previewContext,
+ getPreviewShadeLayoutWide(display!!),
+ SceneContainerFlag.isEnabled,
+ lockId = null,
+ udfpsTop = null,
+ ),
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index c4a7e1ed95e1..55fac3c7644b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransit
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel
@@ -272,6 +273,12 @@ abstract class DeviceEntryIconTransitionModule {
@Binds
@IntoSet
+ abstract fun glanceableHubToLockscreen(
+ impl: GlanceableHubToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun occludedToGlanceableHub(
impl: OccludedToGlanceableHubTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index acd381ec3280..9038922466df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
import com.android.settingslib.Utils
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
@@ -26,6 +27,7 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.recents.utilities.Utilities.clamp
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -46,6 +48,8 @@ constructor(
fingerprintPropertyInteractor: FingerprintPropertyInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
alternateBouncerViewModel: AlternateBouncerViewModel,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val accessibilityInteractor: AccessibilityInteractor,
) {
private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
val alpha: Flow<Float> =
@@ -74,7 +78,15 @@ constructor(
}
}
val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
- flowOf(DeviceEntryIconView.AccessibilityHintType.ENTER)
+ accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+ flowOf(
+ if (touchExplorationEnabled) {
+ DeviceEntryIconView.AccessibilityHintType.BOUNCER
+ } else {
+ DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+ )
+ }
private val fgIconColor: Flow<Int> =
configurationInteractor.onAnyConfigurationChange
@@ -93,6 +105,10 @@ constructor(
)
}
+ fun onTapped() {
+ statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+ }
+
val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
val bgAlpha: Flow<Float> = flowOf(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 1ca08febd7ef..cff651114c93 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -47,7 +47,7 @@ constructor(
/** Reports the alternate bouncer visible state if the scene container flag is enabled. */
val isVisible: Flow<Boolean> =
- alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.assertInNewMode() }
+ alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.unsafeAssertInNewMode() }
/** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */
val transitionToAlternateBouncerProgress: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 0ccb24a9858a..20fc88446ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -61,6 +61,7 @@ constructor(
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
glanceableHubToAodTransitionViewModel: GlanceableHubToAodTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
) {
val color: Flow<Int> =
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
@@ -108,6 +109,7 @@ constructor(
.deviceEntryBackgroundViewAlpha,
lockscreenToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
glanceableHubToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ glanceableHubToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
.onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index b4b4c82c59b9..bcbe66642d11 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -49,7 +50,7 @@ constructor(
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
private val blurFactory: GlanceableHubBlurComponent.Factory,
-) : GlanceableHubTransition {
+) : GlanceableHubTransition, DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow
.setup(
@@ -102,4 +103,8 @@ constructor(
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> = keyguardAlpha
+
+ override val deviceEntryParentViewAlpha: Flow<Float> = keyguardAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index d4676bc9c146..830afeac7b96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -31,10 +31,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
@@ -132,6 +130,8 @@ constructor(
private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val occludedToPrimaryBouncerTransitionViewModel:
+ OccludedToPrimaryBouncerTransitionViewModel,
private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
@@ -163,43 +163,27 @@ constructor(
.onStart { emit(false) }
.distinctUntilChanged()
- private val isOnLockscreen: Flow<Boolean> =
+ private val isOnOrGoingToLockscreen: Flow<Boolean> =
combine(
- keyguardTransitionInteractor.isFinishedIn(LOCKSCREEN).onStart { emit(false) },
- anyOf(
- keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)),
- keyguardTransitionInteractor.isInTransition(Edge.create(from = LOCKSCREEN)),
- ),
- ) { onLockscreen, transitioningToOrFromLockscreen ->
- onLockscreen || transitioningToOrFromLockscreen
+ keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it == 1f },
+ keyguardTransitionInteractor.isInTransition(Edge.create(to = LOCKSCREEN)),
+ ) { onLockscreen, transitioningToLockscreen ->
+ onLockscreen || transitioningToLockscreen
}
.distinctUntilChanged()
private val alphaOnShadeExpansion: Flow<Float> =
combineTransform(
- anyOf(
- keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
- edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
- ),
- keyguardTransitionInteractor.isInTransition(
- edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN),
- edgeWithoutSceneContainer =
- Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
- ),
- keyguardTransitionInteractor.isInTransition(
- Edge.create(from = LOCKSCREEN, to = DREAMING)
- ),
- keyguardTransitionInteractor.isInTransition(
- Edge.create(from = LOCKSCREEN, to = OCCLUDED)
- ),
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = Overlays.Bouncer, to = LOCKSCREEN),
+ edgeWithoutSceneContainer = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
),
- isOnLockscreen,
+ isOnOrGoingToLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
- ) { disabledTransitionRunning, isOnLockscreen, qsExpansion, shadeExpansion ->
+ ) { disabledTransitionRunning, isOnOrGoingToLockscreen, qsExpansion, shadeExpansion ->
// Fade out quickly as the shade expands
- if (isOnLockscreen && !disabledTransitionRunning) {
+ if (isOnOrGoingToLockscreen && !disabledTransitionRunning) {
val alpha =
1f -
MathUtils.constrainedMap(
@@ -288,6 +272,7 @@ constructor(
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToDozingTransitionViewModel.lockscreenAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ occludedToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
offToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToAodTransitionViewModel.lockscreenAlpha,
primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
index 1d2edc6d406b..d4e7af48adfe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
@@ -33,6 +33,9 @@ constructor(
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
+ /** Whether the double tap handling feature should be enabled. */
+ val isDoubleTapHandlingEnabled: Flow<Boolean> = interactor.isDoubleTapHandlingEnabled
+
/** Notifies that the user has long-pressed on the lock screen.
*
* @param isA11yAction: Whether the action was performed as an a11y action
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 3758afa61ed4..9312bca04994 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -66,6 +66,9 @@ constructor(
transitionAnimation.sharedFlow(
duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
onStep = alphaForAnimationStep,
+ // Rapid swipes to bouncer, and may end up skipping intermediate values that would've
+ // caused a complete fade out of lockscreen elements. Ensure it goes to 0f.
+ onFinish = { 0f },
)
val lockscreenAlpha: Flow<Float> = shortcutsAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
index f14a5a282e88..67c3071db390 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
@@ -45,6 +45,12 @@ constructor(
)
.setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, PRIMARY_BOUNCER))
+ /**
+ * Reasserts that lockscreen content should not be visible. It is possible the keyguard alpha is
+ * set to 1f if coming from an expanded shade that collapsed to launch an occluding activity.
+ */
+ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+
override val windowBlurRadius: Flow<Float> =
shadeDependentFlows.transitionFlow(
flowWhenShadeIsExpanded =
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 faa6c52162ce..a85b9b04c1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -327,7 +327,8 @@ public class LogModule {
@SysUISingleton
@KeyguardBlueprintLog
public static LogBuffer provideKeyguardBlueprintLog(LogBufferFactory factory) {
- return factory.create("KeyguardBlueprintLog", 100);
+ // TODO(b/389987229): Reduce back to 100
+ return factory.create("KeyguardBlueprintLog", 1000);
}
/**
@@ -337,7 +338,8 @@ public class LogModule {
@SysUISingleton
@KeyguardClockLog
public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) {
- return factory.create("KeyguardClockLog", 100);
+ // TODO(b/389987229): Reduce back to 100
+ return factory.create("KeyguardClockLog", 1000);
}
/**
@@ -347,7 +349,8 @@ public class LogModule {
@SysUISingleton
@KeyguardSmallClockLog
public static LogBuffer provideKeyguardSmallClockLog(LogBufferFactory factory) {
- return factory.create("KeyguardSmallClockLog", 100);
+ // TODO(b/389987229): Reduce back to 100
+ return factory.create("KeyguardSmallClockLog", 1000);
}
/**
@@ -357,7 +360,8 @@ public class LogModule {
@SysUISingleton
@KeyguardLargeClockLog
public static LogBuffer provideKeyguardLargeClockLog(LogBufferFactory factory) {
- return factory.create("KeyguardLargeClockLog", 100);
+ // TODO(b/389987229): Reduce back to 100
+ return factory.create("KeyguardLargeClockLog", 1000);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
index 4c1da0198498..57d709835b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
@@ -54,7 +54,6 @@ constructor(
job?.cancel()
}
- override fun getStartStrategy(): Int {
- return START_EAGERLY
- }
+ override val startStrategy: Int
+ get() = START_EAGERLY
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
index 7f21d0707f63..5ec81a9a94a1 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
@@ -131,7 +131,7 @@ public class ForceLowLightCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java
index e91be5028777..c1a24e7e020e 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java
@@ -54,7 +54,7 @@ public class LowLightCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
// As this condition keeps the lowlight sensor active, it should only run when needed.
return START_WHEN_NEEDED;
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
index fd6ce1762a28..81572554cb86 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
@@ -70,7 +70,7 @@ public class ScreenSaverEnabledCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/media/NotificationMediaManager.java
index 240953d6bfcd..db4c7a5b2ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationMediaManager.java
@@ -11,12 +11,11 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.statusbar;
+package com.android.systemui.media;
import static com.android.systemui.Flags.mediaControlsUserInitiatedDeleteintent;
-import static com.android.systemui.Flags.notificationMediaManagerBackgroundExecution;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -41,6 +40,8 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.shared.model.MediaData;
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -116,12 +117,7 @@ public class NotificationMediaManager implements Dumpable {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(() -> setMediaMetadata(metadata));
- } else {
- setMediaMetadata(metadata);
- }
-
+ mBackgroundExecutor.execute(() -> setMediaMetadata(metadata));
dispatchUpdateMediaMetaData();
}
};
@@ -283,11 +279,7 @@ public class NotificationMediaManager implements Dumpable {
public void addCallback(MediaListener callback) {
mMediaListeners.add(callback);
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(() -> updateMediaMetaData(callback));
- } else {
- updateMediaMetaData(callback);
- }
+ mBackgroundExecutor.execute(() -> updateMediaMetaData(callback));
}
private void updateMediaMetaData(MediaListener callback) {
@@ -303,55 +295,13 @@ public class NotificationMediaManager implements Dumpable {
public void findAndUpdateMediaNotifications() {
// TODO(b/169655907): get the semi-filtered notifications for current user
Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
- if (notificationMediaManagerBackgroundExecution()) {
- // Create new sbn list to be accessed in background thread.
- List<StatusBarNotification> statusBarNotifications = new ArrayList<>();
- for (NotificationEntry entry: allNotifications) {
- statusBarNotifications.add(entry.getSbn());
- }
- mBackgroundExecutor.execute(() -> findPlayingMediaNotification(statusBarNotifications));
- } else {
- findPlayingMediaNotification(allNotifications);
- }
- dispatchUpdateMediaMetaData();
- }
-
- /**
- * Find a notification and media controller associated with the playing media session, and
- * update this manager's internal state.
- * TODO(b/273443374) check this method
- */
- void findPlayingMediaNotification(@NonNull Collection<NotificationEntry> allNotifications) {
- // Promote the media notification with a controller in 'playing' state, if any.
- NotificationEntry mediaNotification = null;
- MediaController controller = null;
+ // Create new sbn list to be accessed in background thread.
+ List<StatusBarNotification> statusBarNotifications = new ArrayList<>();
for (NotificationEntry entry : allNotifications) {
- Notification notif = entry.getSbn().getNotification();
- if (notif.isMediaNotification()) {
- final MediaSession.Token token =
- entry.getSbn().getNotification().extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class);
- if (token != null) {
- MediaController aController = new MediaController(mContext, token);
- if (PlaybackState.STATE_PLAYING
- == getMediaControllerPlaybackState(aController)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
- + entry.getSbn().getKey());
- }
- mediaNotification = entry;
- controller = aController;
- break;
- }
- }
- }
- }
-
- StatusBarNotification statusBarNotification = null;
- if (mediaNotification != null) {
- statusBarNotification = mediaNotification.getSbn();
+ statusBarNotifications.add(entry.getSbn());
}
- setUpControllerAndKey(controller, statusBarNotification);
+ mBackgroundExecutor.execute(() -> findPlayingMediaNotification(statusBarNotifications));
+ dispatchUpdateMediaMetaData();
}
/**
@@ -415,11 +365,7 @@ public class NotificationMediaManager implements Dumpable {
}
public void clearCurrentMediaNotification() {
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(this::clearMediaNotification);
- } else {
- clearMediaNotification();
- }
+ mBackgroundExecutor.execute(this::clearMediaNotification);
}
private void clearMediaNotification() {
@@ -429,11 +375,7 @@ public class NotificationMediaManager implements Dumpable {
private void dispatchUpdateMediaMetaData() {
ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(() -> updateMediaMetaData(callbacks));
- } else {
- updateMediaMetaData(callbacks);
- }
+ mBackgroundExecutor.execute(() -> updateMediaMetaData(callbacks));
}
private void updateMediaMetaData(List<MediaListener> callbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index e7c2a454e16c..b71d8c995e51 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,6 +17,7 @@
package com.android.systemui.media;
import android.annotation.Nullable;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -126,6 +127,8 @@ public class RingtonePlayer implements CoreStartable {
Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ Binder.getCallingUid() + ")");
}
+ enforceUriUserId(uri);
+
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -207,6 +210,7 @@ public class RingtonePlayer implements CoreStartable {
@Override
public String getTitle(Uri uri) {
+ enforceUriUserId(uri);
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false /*followSettingsUri*/, false /*allowRemote*/);
@@ -214,6 +218,7 @@ public class RingtonePlayer implements CoreStartable {
@Override
public ParcelFileDescriptor openRingtone(Uri uri) {
+ enforceUriUserId(uri);
final UserHandle user = Binder.getCallingUserHandle();
final ContentResolver resolver = getContextForUser(user).getContentResolver();
@@ -241,6 +246,28 @@ public class RingtonePlayer implements CoreStartable {
}
};
+ /**
+ * Must be called from the Binder calling thread.
+ * Ensures caller is from the same userId as the content they're trying to access.
+ * @param uri the URI to check
+ * @throws SecurityException when in a non-system call and userId in uri differs from the
+ * caller's userId
+ */
+ private void enforceUriUserId(Uri uri) throws SecurityException {
+ final int uriUserId = ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId());
+ // for a non-system call, verify the URI to play belongs to the same user as the caller
+ if (UserHandle.isApp(Binder.getCallingUid()) && (UserHandle.myUserId() != uriUserId)) {
+ final String errorMessage = "Illegal access to uri=" + uri
+ + " content associated with user=" + uriUserId
+ + ", current userID: " + UserHandle.myUserId();
+ if (android.media.audio.Flags.ringtoneUserUriCheck()) {
+ throw new SecurityException(errorMessage);
+ } else {
+ Log.e(TAG, errorMessage, new Exception());
+ }
+ }
+ }
+
private Context getContextForUser(UserHandle user) {
try {
return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 46cf0a63e93d..c2efc7559487 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -66,6 +66,7 @@ import com.android.systemui.dagger.qualifiers.Application
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.NotificationMediaManager.isPlayingState
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
@@ -83,7 +84,6 @@ import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
@@ -1256,7 +1256,7 @@ class LegacyMediaDataManagerImpl(
return MediaAction(
Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context),
action,
- context.getString(R.string.controls_media_resume),
+ context.getString(R.string.controls_media_button_play),
if (Flags.mediaControlsUiUpdate()) {
context.getDrawable(R.drawable.ic_media_play_button_container)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index 5fef81f4596a..da462e6713c6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -30,6 +30,8 @@ import android.service.notification.StatusBarNotification
import android.util.Log
import androidx.media.utils.MediaConstants
import com.android.systemui.Flags
+import com.android.systemui.media.NotificationMediaManager.isConnectingState
+import com.android.systemui.media.NotificationMediaManager.isPlayingState
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
import com.android.systemui.media.controls.shared.MediaControlDrawables
@@ -38,8 +40,6 @@ import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaNotificationAction
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.util.kotlin.logI
private const val TAG = "MediaActions"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 8bb7303a8386..a7c5a36b804a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -51,6 +51,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.NotificationMediaManager.isPlayingState
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaData
@@ -60,7 +61,6 @@ import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.kotlin.logD
import java.util.concurrent.ConcurrentHashMap
@@ -521,7 +521,7 @@ constructor(
return MediaAction(
Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context),
action,
- context.getString(R.string.controls_media_resume),
+ context.getString(R.string.controls_media_button_play),
if (Flags.mediaControlsUiUpdate()) {
context.getDrawable(R.drawable.ic_media_play_button_container)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index fe852ce7979f..ca4a65953cba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -66,6 +66,7 @@ import com.android.systemui.dagger.qualifiers.Application
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.NotificationMediaManager.isPlayingState
import com.android.systemui.media.controls.data.repository.MediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
@@ -85,7 +86,6 @@ import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
@@ -1193,7 +1193,7 @@ class MediaDataProcessor(
return MediaAction(
Icon.createWithResource(context, iconId).setTint(themeText).loadDrawable(context),
action,
- context.getString(R.string.controls_media_resume),
+ context.getString(R.string.controls_media_button_play),
if (Flags.mediaControlsUiUpdate()) {
context.getDrawable(R.drawable.ic_media_play_button_container)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 49b53c2d78ae..dfb32e66dae5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -37,6 +37,7 @@ import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.media.flags.Flags
+import com.android.systemui.Flags.mediaControlsDeviceManagerBackgroundExecution
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.shared.MediaControlDrawables
@@ -94,8 +95,39 @@ constructor(
data: MediaData,
immediately: Boolean,
receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
+ isSsReactivated: Boolean,
) {
+ if (mediaControlsDeviceManagerBackgroundExecution()) {
+ bgExecutor.execute { onMediaLoaded(key, oldKey, data) }
+ } else {
+ onMediaLoaded(key, oldKey, data)
+ }
+ }
+
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ if (mediaControlsDeviceManagerBackgroundExecution()) {
+ bgExecutor.execute { onMediaRemoved(key, userInitiated) }
+ } else {
+ onMediaRemoved(key, userInitiated)
+ }
+ }
+
+ fun dump(pw: PrintWriter) {
+ with(pw) {
+ println("MediaDeviceManager state:")
+ entries.forEach { (key, entry) ->
+ println(" key=$key")
+ entry.dump(pw)
+ }
+ }
+ }
+
+ @MainThread
+ private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) {
+ listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) }
+ }
+
+ private fun onMediaLoaded(key: String, oldKey: String?, data: MediaData) {
if (oldKey != null && oldKey != key) {
val oldEntry = entries.remove(oldKey)
oldEntry?.stop()
@@ -104,9 +136,13 @@ constructor(
if (entry == null || entry.token != data.token) {
entry?.stop()
if (data.device != null) {
- // If we were already provided device info (e.g. from RCN), keep that and don't
- // listen for updates, but process once to push updates to listeners
- processDevice(key, oldKey, data.device)
+ // If we were already provided device info (e.g. from RCN), keep that and
+ // don't listen for updates, but process once to push updates to listeners
+ if (mediaControlsDeviceManagerBackgroundExecution()) {
+ fgExecutor.execute { processDevice(key, oldKey, data.device) }
+ } else {
+ processDevice(key, oldKey, data.device)
+ }
return
}
val controller = data.token?.let { controllerFactory.create(it) }
@@ -120,27 +156,18 @@ constructor(
}
}
- override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ private fun onMediaRemoved(key: String, userInitiated: Boolean) {
val token = entries.remove(key)
token?.stop()
- token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
- }
-
- fun dump(pw: PrintWriter) {
- with(pw) {
- println("MediaDeviceManager state:")
- entries.forEach { (key, entry) ->
- println(" key=$key")
- entry.dump(pw)
+ if (mediaControlsDeviceManagerBackgroundExecution()) {
+ fgExecutor.execute {
+ token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
}
+ } else {
+ token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
}
}
- @MainThread
- private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) {
- listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) }
- }
-
interface Listener {
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
@@ -260,7 +287,7 @@ constructor(
override fun onAboutToConnectDeviceAdded(
deviceAddress: String,
deviceName: String,
- deviceIcon: Drawable?
+ deviceIcon: Drawable?,
) {
aboutToConnectDeviceOverride =
AboutToConnectDevice(
@@ -270,8 +297,8 @@ constructor(
/* enabled */ enabled = true,
/* icon */ deviceIcon,
/* name */ deviceName,
- /* showBroadcastButton */ showBroadcastButton = false
- )
+ /* showBroadcastButton */ showBroadcastButton = false,
+ ),
)
updateCurrent()
}
@@ -292,7 +319,7 @@ constructor(
override fun onBroadcastMetadataChanged(
broadcastId: Int,
- metadata: BluetoothLeBroadcastMetadata
+ metadata: BluetoothLeBroadcastMetadata,
) {
logger.logBroadcastMetadataChanged(broadcastId, metadata.toString())
updateCurrent()
@@ -352,14 +379,14 @@ constructor(
// route.
connectedDevice?.copy(
name = it.name ?: connectedDevice.name,
- icon = icon
+ icon = icon,
)
}
?: MediaDeviceData(
enabled = false,
icon = MediaControlDrawables.getHomeDevices(context),
name = context.getString(R.string.media_seamless_other_device),
- showBroadcastButton = false
+ showBroadcastButton = false,
)
logger.logRemoteDevice(routingSession?.name, connectedDevice)
} else {
@@ -398,7 +425,7 @@ constructor(
device?.iconWithoutBackground,
name,
id = device?.id,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
}
}
@@ -415,7 +442,7 @@ constructor(
icon = iconWithoutBackground,
name = name,
id = id,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
private fun getLeAudioBroadcastDeviceData(): MediaDeviceData {
@@ -425,7 +452,7 @@ constructor(
icon = MediaControlDrawables.getLeAudioSharing(context),
name = context.getString(R.string.audio_sharing_description),
intent = null,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
} else {
MediaDeviceData(
@@ -433,7 +460,7 @@ constructor(
icon = MediaControlDrawables.getAntenna(context),
name = broadcastDescription,
intent = null,
- showBroadcastButton = true
+ showBroadcastButton = true,
)
}
}
@@ -449,7 +476,7 @@ constructor(
device,
controller,
routingSession?.name,
- selectedRoutes?.firstOrNull()?.name
+ selectedRoutes?.firstOrNull()?.name,
)
if (controller == null) {
@@ -514,7 +541,7 @@ constructor(
MediaDataUtils.getAppLabel(
context,
localMediaManager.packageName,
- context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name)
+ context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name),
)
val isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
if (isCurrentBroadcastedApp) {
@@ -538,5 +565,5 @@ constructor(
*/
private data class AboutToConnectDevice(
val fullMediaDevice: MediaDevice? = null,
- val backupMediaDeviceData: MediaDeviceData? = null
+ val backupMediaDeviceData: MediaDeviceData? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index 684a560b0502..be4e6cc59f76 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -25,12 +25,12 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.NotificationMediaManager.isPlayingState
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
deleted file mode 100644
index 0cb36edfd382..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.domain.pipeline.interactor
-
-import android.content.Context
-import android.content.Intent
-import android.provider.Settings
-import android.util.Log
-import androidx.annotation.VisibleForTesting
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.Expandable
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.controls.data.repository.MediaFilterRepository
-import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
-import com.android.systemui.media.controls.shared.model.MediaRecModel
-import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.plugins.ActivityStarter
-import java.net.URISyntaxException
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Encapsulates business logic for media recommendation */
-@SysUISingleton
-class MediaRecommendationsInteractor
-@Inject
-constructor(
- @Application applicationScope: CoroutineScope,
- @Application private val applicationContext: Context,
- private val repository: MediaFilterRepository,
- private val mediaDataProcessor: MediaDataProcessor,
- private val broadcastSender: BroadcastSender,
- private val activityStarter: ActivityStarter,
-) {
-
- val recommendations: Flow<MediaRecommendationsModel> =
- repository.smartspaceMediaData.map { toRecommendationsModel(it) }.distinctUntilChanged()
-
- /** Indicates whether the recommendations card is active. */
- val isActive: StateFlow<Boolean> =
- repository.smartspaceMediaData
- .map { it.isActive }
- .distinctUntilChanged()
- .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
-
- fun removeMediaRecommendations(key: String, dismissIntent: Intent?, delayMs: Long) {
- mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
- if (dismissIntent == null) {
- Log.w(TAG, "Cannot create dismiss action click action: extras missing dismiss_intent.")
- return
- }
-
- val className = dismissIntent.component?.className
- if (className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
- // Dismiss the card Smartspace data through Smartspace trampoline activity.
- applicationContext.startActivity(dismissIntent)
- } else {
- broadcastSender.sendBroadcast(dismissIntent)
- }
- }
-
- fun startSettings() {
- activityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */ true)
- }
-
- fun startClickIntent(expandable: Expandable, intent: Intent) {
- if (shouldActivityOpenInForeground(intent)) {
- // Request to unlock the device if the activity needs to be opened in foreground.
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0 /* delay */,
- expandable.activityTransitionController(
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
- ),
- )
- } else {
- // Otherwise, open the activity in background directly.
- applicationContext.startActivity(intent)
- }
- }
-
- /** Returns if the action will open the activity in foreground. */
- private fun shouldActivityOpenInForeground(intent: Intent): Boolean {
- val intentString = intent.extras?.getString(EXTRAS_SMARTSPACE_INTENT) ?: return false
- try {
- val wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME)
- return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false)
- } catch (e: URISyntaxException) {
- Log.wtf(TAG, "Failed to create intent from URI: $intentString")
- e.printStackTrace()
- }
- return false
- }
-
- private fun toRecommendationsModel(data: SmartspaceMediaData): MediaRecommendationsModel {
- val mediaRecs = ArrayList<MediaRecModel>()
- data.recommendations.forEach {
- with(it) { mediaRecs.add(MediaRecModel(intent, title, subtitle, icon, extras)) }
- }
- return with(data) {
- MediaRecommendationsModel(
- key = targetId,
- uid = getUid(applicationContext),
- packageName = packageName,
- instanceId = instanceId,
- appName = getAppName(applicationContext),
- dismissIntent = dismissIntent,
- areRecommendationsValid = isValid(),
- mediaRecs = mediaRecs,
- )
- }
- }
-
- fun switchToMediaControl(packageName: String) {
- repository.setMediaFromRecPackageName(packageName)
- }
-
- companion object {
-
- private const val TAG = "MediaRecommendationsInteractor"
-
- // TODO (b/237284176) : move AGSA reference out.
- private const val EXTRAS_SMARTSPACE_INTENT =
- "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"
- @VisibleForTesting
- const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME =
- "com.google.android.apps.gsa.staticplugins.opa.smartspace." +
- "ExportedSmartspaceTrampolineActivity"
-
- private const val KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"
-
- private val SETTINGS_INTENT = Intent(Settings.ACTION_MEDIA_CONTROLS_SETTINGS)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
deleted file mode 100644
index 4877d18de7ab..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.binder
-
-import android.app.WallpaperColors
-import android.content.Context
-import android.content.res.ColorStateList
-import android.content.res.Configuration
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.Matrix
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.ColorDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.Icon
-import android.graphics.drawable.LayerDrawable
-import android.os.Trace
-import android.util.TypedValue
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.animation.Expandable
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
-import com.android.systemui.media.controls.ui.animation.surfaceFromScheme
-import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme
-import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme
-import com.android.systemui.media.controls.ui.controller.MediaViewController
-import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
-import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel
-import com.android.systemui.monet.ColorScheme
-import com.android.systemui.monet.Style
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.res.R
-import com.android.systemui.util.animation.TransitionLayout
-import kotlin.math.min
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
-import kotlinx.coroutines.withContext
-
-private const val TAG = "MediaRecommendationsViewBinder"
-private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f
-private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f
-
-object MediaRecommendationsViewBinder {
-
- /** Binds recommendations view holder to the given view-model */
- fun bind(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecommendationsViewModel,
- mediaViewController: MediaViewController,
- falsingManager: FalsingManager,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility
- val cardView = viewHolder.recommendations
- cardView.repeatWhenAttached {
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.mediaRecsCard.collectLatest { viewModel ->
- viewModel?.let {
- bindRecsCard(
- viewHolder,
- it,
- mediaViewController,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- }
- }
- }
- }
- }
- }
- }
-
- suspend fun bindRecsCard(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecsCardViewModel,
- viewController: MediaViewController,
- falsingManager: FalsingManager,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- // Set up media control location and its listener.
- viewModel.onLocationChanged(viewController.currentEndLocation)
- viewController.locationChangeListener = viewModel.onLocationChanged
-
- // Bind main card.
- viewHolder.recommendations.contentDescription =
- viewModel.contentDescription.invoke(viewController.isGutsVisible)
-
- viewHolder.recommendations.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
- viewModel.onClicked(Expandable.fromView(it))
- }
-
- viewHolder.recommendations.setOnLongClickListener {
- if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
- return@setOnLongClickListener true
- if (!viewController.isGutsVisible) {
- openGuts(viewHolder, viewModel, viewController)
- } else {
- closeGuts(viewHolder, viewModel, viewController)
- }
- return@setOnLongClickListener true
- }
-
- // Bind colors
- val appIcon = viewModel.mediaRecs.first().appIcon
- fetchAndUpdateColors(viewHolder, appIcon, backgroundDispatcher, mainDispatcher)
- // Bind all recommendations.
- bindRecommendationsList(
- viewHolder,
- viewModel.mediaRecs,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- updateRecommendationsVisibility(viewController, viewHolder.recommendations)
-
- // Set visibility of recommendations.
- val expandedSet: ConstraintSet = viewController.expandedLayout
- val collapsedSet: ConstraintSet = viewController.collapsedLayout
- viewHolder.mediaTitles.forEach {
- setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible)
- setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible)
- }
- viewHolder.mediaSubtitles.forEach {
- setVisibleAndAlpha(expandedSet, it.id, viewModel.areSubtitlesVisible)
- setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible)
- }
-
- bindRecommendationsGuts(viewHolder, viewModel, viewController, falsingManager)
-
- viewController.refreshState()
- }
-
- private fun bindRecommendationsGuts(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecsCardViewModel,
- viewController: MediaViewController,
- falsingManager: FalsingManager,
- ) {
- val gutsViewHolder = viewHolder.gutsViewHolder
- val gutsViewModel = viewModel.gutsMenu
-
- gutsViewHolder.gutsText.text = gutsViewModel.gutsText
- gutsViewHolder.dismissText.visibility = View.VISIBLE
- gutsViewHolder.dismiss.isEnabled = true
- gutsViewHolder.dismiss.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
- closeGuts(viewHolder, viewModel, viewController)
- gutsViewModel.onDismissClicked()
- }
-
- gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground
- gutsViewHolder.cancel.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- closeGuts(viewHolder, viewModel, viewController)
- }
- }
-
- gutsViewHolder.settings.setOnClickListener {
- if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- gutsViewModel.onSettingsClicked.invoke()
- }
- }
-
- gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled)
- }
-
- private suspend fun bindRecommendationsList(
- viewHolder: RecommendationViewHolder,
- mediaRecs: List<MediaRecViewModel>,
- falsingManager: FalsingManager,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- mediaRecs.forEachIndexed { index, mediaRecViewModel ->
- if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed
-
- val appIconView = viewHolder.mediaAppIcons[index]
- appIconView.clearColorFilter()
- appIconView.setImageDrawable(mediaRecViewModel.appIcon)
-
- val mediaCoverContainer = viewHolder.mediaCoverContainers[index]
- mediaCoverContainer.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
- mediaRecViewModel.onClicked(Expandable.fromView(it), index)
- }
- mediaCoverContainer.setOnLongClickListener {
- if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
- return@setOnLongClickListener true
- (it.parent as View).performLongClick()
- return@setOnLongClickListener true
- }
-
- val mediaCover = viewHolder.mediaCoverItems[index]
- bindRecommendationArtwork(
- mediaCover.context,
- viewHolder,
- mediaRecViewModel,
- index,
- backgroundDispatcher,
- mainDispatcher,
- )
- mediaCover.contentDescription = mediaRecViewModel.contentDescription
-
- val title = viewHolder.mediaTitles[index]
- title.text = mediaRecViewModel.title
-
- val subtitle = viewHolder.mediaSubtitles[index]
- subtitle.text = mediaRecViewModel.subtitle
-
- val progressBar = viewHolder.mediaProgressBars[index]
- progressBar.progress = mediaRecViewModel.progress
- if (mediaRecViewModel.progress == 0) {
- progressBar.visibility = View.GONE
- }
- }
- }
-
- private fun openGuts(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecsCardViewModel,
- mediaViewController: MediaViewController,
- ) {
- viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
- mediaViewController.openGuts()
- viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(true)
- viewModel.onLongClicked.invoke()
- }
-
- private fun closeGuts(
- viewHolder: RecommendationViewHolder,
- mediaRecsCardViewModel: MediaRecsCardViewModel,
- mediaViewController: MediaViewController,
- ) {
- viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
- mediaViewController.closeGuts(false)
- viewHolder.recommendations.contentDescription =
- mediaRecsCardViewModel.contentDescription.invoke(false)
- }
-
- private fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
- set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else ConstraintSet.GONE)
- set.setAlpha(resId, if (visible) 1.0f else 0.0f)
- }
-
- fun updateRecommendationsVisibility(
- mediaViewController: MediaViewController,
- cardView: TransitionLayout,
- ) {
- val fittedRecsNum = getNumberOfFittedRecommendations(cardView.context)
- val expandedSet = mediaViewController.expandedLayout
- val collapsedSet = mediaViewController.collapsedLayout
- val mediaCoverContainers = getMediaCoverContainers(cardView)
- // Hide media cover that cannot fit in the recommendation card.
- mediaCoverContainers.forEachIndexed { index, container ->
- setVisibleAndAlpha(expandedSet, container.id, index < fittedRecsNum)
- setVisibleAndAlpha(collapsedSet, container.id, index < fittedRecsNum)
- }
- }
-
- private fun getMediaCoverContainers(cardView: TransitionLayout): List<ViewGroup> {
- return listOf<ViewGroup>(
- cardView.requireViewById(R.id.media_cover1_container),
- cardView.requireViewById(R.id.media_cover2_container),
- cardView.requireViewById(R.id.media_cover3_container),
- )
- }
-
- private fun getNumberOfFittedRecommendations(context: Context): Int {
- val res = context.resources
- val config = res.configuration
- val defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp)
- val recCoverWidth =
- (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
- res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
-
- // On landscape, media controls should take half of the screen width.
- val displayAvailableDpWidth =
- if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- config.screenWidthDp / 2
- } else {
- config.screenWidthDp
- }
- val fittedNum =
- if (displayAvailableDpWidth > defaultDpWidth) {
- val recCoverDefaultWidth =
- res.getDimensionPixelSize(R.dimen.qs_media_rec_default_width)
- recCoverDefaultWidth / recCoverWidth
- } else {
- val displayAvailableWidth =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- displayAvailableDpWidth.toFloat(),
- res.displayMetrics,
- )
- .toInt()
- displayAvailableWidth / recCoverWidth
- }
- return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt()
- }
-
- private suspend fun bindRecommendationArtwork(
- context: Context,
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecViewModel,
- index: Int,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- val traceCookie = viewHolder.hashCode()
- val traceName = "MediaRecommendationsViewBinder#bindRecommendationArtwork"
- Trace.beginAsyncSection(traceName, traceCookie)
-
- // Capture width & height from views in foreground for artwork scaling in background
- val width = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
- val height =
- context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_height_expanded)
-
- withContext(backgroundDispatcher) {
- val artwork =
- getRecCoverBackground(
- context,
- viewModel.albumIcon,
- width,
- height,
- backgroundDispatcher,
- )
- withContext(mainDispatcher) {
- val mediaCover = viewHolder.mediaCoverItems[index]
- val coverMatrix = Matrix(mediaCover.imageMatrix)
- coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height)
- mediaCover.imageMatrix = coverMatrix
- mediaCover.setImageDrawable(artwork)
- }
- }
- }
-
- /** Returns the recommendation album cover of [width]x[height] size. */
- private suspend fun getRecCoverBackground(
- context: Context,
- icon: Icon?,
- width: Int,
- height: Int,
- backgroundDispatcher: CoroutineDispatcher,
- ): Drawable =
- withContext(backgroundDispatcher) {
- return@withContext MediaArtworkHelper.getWallpaperColor(
- context,
- backgroundDispatcher,
- icon,
- TAG,
- )
- ?.let { wallpaperColors ->
- addGradientToRecommendationAlbum(
- context,
- icon!!,
- ColorScheme(wallpaperColors, true, Style.CONTENT),
- width,
- height,
- )
- } ?: ColorDrawable(Color.TRANSPARENT)
- }
-
- private fun addGradientToRecommendationAlbum(
- context: Context,
- artworkIcon: Icon,
- mutableColorScheme: ColorScheme,
- width: Int,
- height: Int,
- ): LayerDrawable {
- // First try scaling rec card using bitmap drawable.
- // If returns null, set drawable bounds.
- val albumArt =
- getScaledRecommendationCover(context, artworkIcon, width, height)
- ?: MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
- val gradient =
- AppCompatResources.getDrawable(context, R.drawable.qs_media_rec_scrim)?.mutate()
- as GradientDrawable
- return MediaArtworkHelper.setUpGradientColorOnDrawable(
- albumArt,
- gradient,
- mutableColorScheme,
- MEDIA_REC_SCRIM_START_ALPHA,
- MEDIA_REC_SCRIM_END_ALPHA,
- )
- }
-
- /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */
- private fun getScaledRecommendationCover(
- context: Context,
- artworkIcon: Icon,
- width: Int,
- height: Int,
- ): Drawable? {
- check(width > 0) { "Width must be a positive number but was $width" }
- check(height > 0) { "Height must be a positive number but was $height" }
-
- return if (
- artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP
- ) {
- artworkIcon.bitmap?.let {
- val bitmap = Bitmap.createScaledBitmap(it, width, height, false)
- BitmapDrawable(context.resources, bitmap)
- }
- } else {
- null
- }
- }
-
- private suspend fun fetchAndUpdateColors(
- viewHolder: RecommendationViewHolder,
- appIcon: Drawable,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) =
- withContext(backgroundDispatcher) {
- val colorScheme =
- ColorScheme(WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true)
- withContext(mainDispatcher) {
- val backgroundColor = surfaceFromScheme(colorScheme)
- val textPrimaryColor = textPrimaryFromScheme(colorScheme)
- val textSecondaryColor = textSecondaryFromScheme(colorScheme)
-
- viewHolder.cardTitle.setTextColor(textPrimaryColor)
- viewHolder.recommendations.setBackgroundTintList(
- ColorStateList.valueOf(backgroundColor)
- )
-
- viewHolder.mediaTitles.forEach { it.setTextColor(textPrimaryColor) }
- viewHolder.mediaSubtitles.forEach { it.setTextColor(textSecondaryColor) }
- viewHolder.mediaProgressBars.forEach {
- it.progressTintList = ColorStateList.valueOf(textPrimaryColor)
- }
-
- viewHolder.gutsViewHolder.setColors(colorScheme)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index cedf661d8ee3..5b65531cdd55 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -127,7 +127,6 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
holder.seekBar.setMax(data.duration)
- val totalTimeDescription = data.durationDescription
if (data.scrubbing) {
holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration)
}
@@ -147,17 +146,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
}
- val elapsedTimeDescription = data.elapsedTimeDescription
if (data.scrubbing) {
holder.scrubbingElapsedTimeView.text = formatTimeLabel(it)
}
-
- holder.seekBar.contentDescription =
- holder.seekBar.context.getString(
- R.string.controls_media_seekbar_description,
- elapsedTimeDescription,
- totalTimeDescription,
- )
}
}
@@ -166,6 +157,18 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
}
+ fun updateContentDescription(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ ) {
+ holder.seekBar.contentDescription =
+ holder.seekBar.context.getString(
+ R.string.controls_media_seekbar_description,
+ elapsedTimeDescription,
+ durationDescription,
+ )
+ }
+
@VisibleForTesting
open fun buildResetAnimator(targetTime: Int): Animator {
val animator =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 7b1ae57ed421..71b3223b77be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -61,14 +61,12 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
-import com.android.systemui.media.controls.ui.binder.MediaRecommendationsViewBinder
import com.android.systemui.media.controls.ui.util.MediaViewModelCallback
import com.android.systemui.media.controls.ui.util.MediaViewModelListUpdateCallback
import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaScrollView
import com.android.systemui.media.controls.ui.view.MediaViewHolder
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaCarouselViewModel
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -161,8 +159,8 @@ constructor(
/** Is the player currently visible (at the end of the transformation */
private var playersVisible: Boolean = false
- /** Are we currently disabling pagination only allowing one media session to show */
- private var currentlyDisablePagination: Boolean = false
+ /** Are we currently disabling scolling, only allowing the first media session to show */
+ private var currentlyDisableScrolling: Boolean = false
/**
* The desired location where we'll be at the end of the transformation. Usually this matches
@@ -478,41 +476,10 @@ constructor(
MediaPlayerData.isSwipedAway = false
}
- override fun onSmartspaceMediaDataLoaded(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean,
- ) {
- debugLogger.logRecommendationLoaded(key, data.isActive)
- // Log the case where the hidden media carousel with the existed inactive resume
- // media is shown by the Smartspace signal.
- if (data.isActive) {
- addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
- } else {
- // Handle update to inactive as a removal
- onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
- }
- MediaPlayerData.isSwipedAway = false
- }
-
override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
debugLogger.logMediaRemoved(key, userInitiated)
removePlayer(key, userInitiated = userInitiated)
}
-
- 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
- updateHostVisibility()
- }
- } else {
- keysNeedRemoval.add(key)
- }
- }
}
)
}
@@ -655,22 +622,6 @@ constructor(
mediaContent.addView(viewHolder.player, position)
controllerById[commonViewModel.instanceId.toString()] = viewController
}
- is MediaCommonViewModel.MediaRecommendations -> {
- val viewHolder =
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
- viewController.attachRecommendations(viewHolder)
- viewController.recommendationViewHolder?.recommendations?.layoutParams = lp
- MediaRecommendationsViewBinder.bind(
- viewHolder,
- commonViewModel.recsViewModel,
- viewController,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- mediaContent.addView(viewHolder.recommendations, position)
- controllerById[commonViewModel.key] = viewController
- }
}
viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
updateViewControllerToState(viewController, noAnimation = true)
@@ -695,21 +646,10 @@ constructor(
}
private fun onRemoved(commonViewModel: MediaCommonViewModel) {
- val id =
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString()
- is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key
- }
+ val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString()
controllerById.remove(id)?.let {
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> {
- mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player)
- mediaContent.removeView(it.mediaViewHolder!!.player)
- }
- is MediaCommonViewModel.MediaRecommendations -> {
- mediaContent.removeView(it.recommendationViewHolder!!.recommendations)
- }
- }
+ mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player)
+ mediaContent.removeView(it.mediaViewHolder!!.player)
it.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
@@ -718,21 +658,10 @@ constructor(
}
private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) {
- val id =
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString()
- is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key
- }
+ val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString()
controllerById[id]?.let {
mediaContent.removeViewAt(from)
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> {
- mediaContent.addView(it.mediaViewHolder!!.player, to)
- }
- is MediaCommonViewModel.MediaRecommendations -> {
- mediaContent.addView(it.recommendationViewHolder!!.recommendations, to)
- }
- }
+ mediaContent.addView(it.mediaViewHolder!!.player, to)
}
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
@@ -746,11 +675,9 @@ constructor(
val viewIds =
viewModels
.map { mediaCommonViewModel ->
- when (mediaCommonViewModel) {
- is MediaCommonViewModel.MediaControl ->
- mediaCommonViewModel.instanceId.toString()
- is MediaCommonViewModel.MediaRecommendations -> mediaCommonViewModel.key
- }
+ (mediaCommonViewModel as MediaCommonViewModel.MediaControl)
+ .instanceId
+ .toString()
}
.toHashSet()
controllerById
@@ -758,7 +685,6 @@ constructor(
.forEach {
mediaCarouselScrollHandler.onPrePlayerRemoved(it.value.mediaViewHolder?.player)
mediaContent.removeView(it.value.mediaViewHolder?.player)
- mediaContent.removeView(it.value.recommendationViewHolder?.recommendations)
it.value.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
@@ -808,9 +734,6 @@ constructor(
mediaContent.removeAllViews()
for (mediaPlayer in MediaPlayerData.players()) {
mediaPlayer.mediaViewHolder?.let { mediaContent.addView(it.player) }
- ?: mediaPlayer.recommendationViewHolder?.let {
- mediaContent.addView(it.recommendations)
- }
}
mediaCarouselScrollHandler.onPlayersChanged()
mediaControlChipInteractor.updateMediaControlChipModelLegacy(
@@ -980,67 +903,6 @@ constructor(
return MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
}
- private fun addSmartspaceMediaRecommendations(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean,
- ) =
- traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
- if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
- MediaPlayerData.getMediaPlayer(key)?.let {
- Log.w(TAG, "Skip adding smartspace target in carousel")
- return
- }
-
- val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
- existingSmartspaceMediaKey?.let {
- val removedPlayer =
- removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
- removedPlayer?.run {
- debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
- onDestroy()
- }
- }
-
- 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,
- )
- updateViewControllerToState(newRecs.mediaViewController, 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.e(
- TAG,
- "Size of players list and number of views in carousel are out of sync. " +
- "Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}.",
- )
- }
- }
-
fun removePlayer(
key: String,
dismissMediaData: Boolean = true,
@@ -1057,7 +919,6 @@ constructor(
return removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed.mediaViewHolder?.player)
mediaContent.removeView(removed.mediaViewHolder?.player)
- mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
removed.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
mediaControlChipInteractor.updateMediaControlChipModelLegacy(
@@ -1095,31 +956,18 @@ constructor(
val mediaDataList = MediaPlayerData.mediaData()
// Do not loop through the original list of media data because the re-addition of media data
// is being executed in background thread.
- mediaDataList.forEach { (key, data, isSsMediaRec) ->
- if (isSsMediaRec) {
- val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
+ mediaDataList.forEach { (key, data, _) ->
+ val isSsReactivated = MediaPlayerData.isSsReactivated(key)
+ if (recreateMedia) {
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
- smartspaceMediaData?.let {
- addSmartspaceMediaRecommendations(
- it.targetId,
- it,
- MediaPlayerData.shouldPrioritizeSs,
- )
- }
- onUiExecutionEnd.run()
- } else {
- val isSsReactivated = MediaPlayerData.isSsReactivated(key)
- if (recreateMedia) {
- removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
- }
- addOrUpdatePlayer(
- key = key,
- oldKey = null,
- data = data,
- isSsReactivated = isSsReactivated,
- onUiExecutionEnd = onUiExecutionEnd,
- )
}
+ addOrUpdatePlayer(
+ key = key,
+ oldKey = null,
+ data = data,
+ isSsReactivated = isSsReactivated,
+ onUiExecutionEnd = onUiExecutionEnd,
+ )
}
}
@@ -1129,12 +977,8 @@ constructor(
if (recreateMedia) {
mediaContent.removeAllViews()
commonViewModels.forEachIndexed { index, viewModel ->
- when (viewModel) {
- is MediaCommonViewModel.MediaControl ->
- controllerById[viewModel.instanceId.toString()]?.onDestroy()
- is MediaCommonViewModel.MediaRecommendations ->
- controllerById[viewModel.key]?.onDestroy()
- }
+ val mediaControlViewModel = (viewModel as MediaCommonViewModel.MediaControl)
+ controllerById[mediaControlViewModel.instanceId.toString()]?.onDestroy()
onAdded(viewModel, index, configChanged = true)
}
}
@@ -1286,21 +1130,22 @@ constructor(
val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true
val startShowsActive =
hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive
- val startDisablePagination = hostStates[currentStartLocation]?.disablePagination ?: false
- val endDisablePagination = hostStates[currentEndLocation]?.disablePagination ?: false
+ val startDisableScrolling = hostStates[currentStartLocation]?.disableScrolling ?: false
+ val endDisableScrolling = hostStates[currentEndLocation]?.disableScrolling ?: false
if (
currentlyShowingOnlyActive != endShowsActive ||
- currentlyDisablePagination != endDisablePagination ||
+ currentlyDisableScrolling != endDisableScrolling ||
((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
(startShowsActive != endShowsActive ||
- startDisablePagination != endDisablePagination))
+ startDisableScrolling != endDisableScrolling))
) {
// Whenever we're transitioning from between differing states or the endstate differs
// we reset the translation
currentlyShowingOnlyActive = endShowsActive
- currentlyDisablePagination = endDisablePagination
+ currentlyDisableScrolling = endDisableScrolling
mediaCarouselScrollHandler.resetTranslation(animate = true)
+ mediaCarouselScrollHandler.scrollingDisabled = currentlyDisableScrolling
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index 5d62c022efba..365389107648 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -64,28 +64,6 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) {
{ "removing player $str1, by user $bool1" },
)
- fun logRecommendationLoaded(key: String, isActive: Boolean) =
- buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = isActive
- },
- { "add recommendation $str1, active $bool1" },
- )
-
- fun logRecommendationRemoved(key: String, immediately: Boolean) =
- buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = immediately
- },
- { "removing recommendation $str1, immediate=$bool1" },
- )
-
fun logCarouselHidden() = buffer.log(TAG, LogLevel.DEBUG, {}, { "hiding carousel" })
fun logCarouselVisible() = buffer.log(TAG, LogLevel.DEBUG, {}, { "showing carousel" })
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index a6bf5f43698b..f69985ee5364 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -22,7 +22,6 @@ import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
import static com.android.systemui.Flags.communalHub;
import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions;
-import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA;
import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY;
import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA;
@@ -35,24 +34,17 @@ import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.WallpaperColors;
-import android.app.smartspace.SmartspaceAction;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -69,14 +61,12 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -105,7 +95,6 @@ import com.android.systemui.media.controls.shared.model.MediaAction;
import com.android.systemui.media.controls.shared.model.MediaButton;
import com.android.systemui.media.controls.shared.model.MediaData;
import com.android.systemui.media.controls.shared.model.MediaDeviceData;
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
import com.android.systemui.media.controls.ui.animation.AnimationBindHandler;
import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition;
import com.android.systemui.media.controls.ui.animation.MediaColorSchemesKt;
@@ -113,7 +102,6 @@ import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler
import com.android.systemui.media.controls.ui.binder.SeekBarObserver;
import com.android.systemui.media.controls.ui.view.GutsViewHolder;
import com.android.systemui.media.controls.ui.view.MediaViewHolder;
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
@@ -143,14 +131,12 @@ import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -165,17 +151,6 @@ public class MediaControlPanel {
protected static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
- private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
- + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity";
- private static final String EXTRAS_SMARTSPACE_INTENT =
- "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";
-
- private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
- private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
- private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
-
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
// Buttons to show in small player when using semantic actions
@@ -215,17 +190,14 @@ public class MediaControlPanel {
private Context mContext;
private MediaViewHolder mMediaViewHolder;
- private RecommendationViewHolder mRecommendationViewHolder;
private String mKey;
private MediaData mMediaData;
- private SmartspaceMediaData mRecommendationData;
private MediaViewController mMediaViewController;
private MediaSession.Token mToken;
private MediaController mController;
private Lazy<MediaDataManager> mMediaDataManagerLazy;
// Uid for the media app.
protected int mUid = Process.INVALID_UID;
- private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogManager mMediaOutputDialogManager;
private final FalsingManager mFalsingManager;
@@ -241,7 +213,6 @@ public class MediaControlPanel {
private final NotificationLockscreenUserManager mLockscreenUserManager;
// Used for logging.
- private SystemClock mSystemClock;
private MediaUiEventLogger mLogger;
private InstanceId mInstanceId;
private String mPackageName;
@@ -253,6 +224,8 @@ public class MediaControlPanel {
this::setIsScrubbing;
private final SeekBarViewModel.EnabledChangeListener mEnabledChangeListener =
this::setIsSeekBarEnabled;
+ private final SeekBarViewModel.ContentDescriptionListener mContentDescriptionListener =
+ this::setSeekbarContentDescription;
private final BroadcastDialogController mBroadcastDialogController;
private boolean mIsCurrentBroadcastedApp = false;
@@ -310,7 +283,6 @@ public class MediaControlPanel {
MediaOutputDialogManager mediaOutputDialogManager,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager,
- SystemClock systemClock,
MediaUiEventLogger logger,
KeyguardStateController keyguardStateController,
ActivityIntentHelper activityIntentHelper,
@@ -330,7 +302,6 @@ public class MediaControlPanel {
mMediaOutputDialogManager = mediaOutputDialogManager;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
- mSystemClock = systemClock;
mLogger = logger;
mKeyguardStateController = keyguardStateController;
mActivityIntentHelper = activityIntentHelper;
@@ -358,6 +329,7 @@ public class MediaControlPanel {
}
mSeekBarViewModel.removeScrubbingChangeListener(mScrubbingChangeListener);
mSeekBarViewModel.removeEnabledChangeListener(mEnabledChangeListener);
+ mSeekBarViewModel.removeContentDescriptionListener(mContentDescriptionListener);
mSeekBarViewModel.onDestroy();
mMediaViewController.onDestroy();
}
@@ -373,16 +345,6 @@ public class MediaControlPanel {
}
/**
- * Get the recommendation view holder used to display Smartspace media recs.
- *
- * @return the recommendation view holder
- */
- @Nullable
- public RecommendationViewHolder getRecommendationViewHolder() {
- return mRecommendationViewHolder;
- }
-
- /**
* Get the view controller used to display media controls
*
* @return the media view controller
@@ -436,6 +398,10 @@ public class MediaControlPanel {
});
}
+ private void setSeekbarContentDescription(CharSequence elapsedTime, CharSequence duration) {
+ mSeekBarObserver.updateContentDescription(elapsedTime, duration);
+ }
+
/**
* Reloads animator duration scale.
*/
@@ -465,7 +431,8 @@ public class MediaControlPanel {
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener);
mSeekBarViewModel.setEnabledChangeListener(mEnabledChangeListener);
- mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
+ mSeekBarViewModel.setContentDescriptionListener(mContentDescriptionListener);
+ mMediaViewController.attach(player);
vh.getPlayer().setOnLongClickListener(v -> {
if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
@@ -522,26 +489,6 @@ public class MediaControlPanel {
return result;
}
- /** Attaches the recommendations to the recommendation view holder. */
- public void attachRecommendation(RecommendationViewHolder vh) {
- mRecommendationViewHolder = vh;
- TransitionLayout recommendations = vh.getRecommendations();
-
- mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION);
- mMediaViewController.configurationChangeListener = this::updateRecommendationsVisibility;
-
- mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> {
- if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
- if (!mMediaViewController.isGutsVisible()) {
- openGuts();
- return true;
- } else {
- closeGuts();
- return true;
- }
- });
- }
-
/** Bind this player view based on the data given. */
public void bindPlayer(@NonNull MediaData data, String key) {
SceneContainerFlag.assertInLegacyMode();
@@ -868,24 +815,6 @@ public class MediaControlPanel {
mMediaViewHolder.getPlayer().setContentDescription(contentDescription);
}
- private void bindRecommendationContentDescription(SmartspaceMediaData data) {
- if (mRecommendationViewHolder == null) {
- return;
- }
-
- CharSequence contentDescription;
- if (mMediaViewController.isGutsVisible()) {
- contentDescription =
- mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
- } else if (data != null) {
- contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
- } else {
- contentDescription = null;
- }
-
- mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription);
- }
-
private void bindArtworkAndColors(MediaData data, String key, boolean updateBackground) {
final int traceCookie = data.hashCode();
final String traceName = "MediaControlPanel#bindArtworkAndColors<" + key + ">";
@@ -993,62 +922,6 @@ public class MediaControlPanel {
});
}
- private void bindRecommendationArtwork(
- SmartspaceAction recommendation,
- String packageName,
- int itemIndex
- ) {
- final int traceCookie = recommendation.hashCode();
- final String traceName =
- "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
- Trace.beginAsyncSection(traceName, traceCookie);
-
- // Capture width & height from views in foreground for artwork scaling in background
- int width = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_rec_album_width);
- int height = mContext.getResources().getDimensionPixelSize(
- R.dimen.qs_media_rec_album_height_expanded);
-
- mBackgroundExecutor.execute(() -> {
- // Album art
- ColorScheme mutableColorScheme = null;
- Drawable artwork;
- Icon artworkIcon = recommendation.getIcon();
- WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
- if (wallpaperColors != null) {
- mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = addGradientToRecommendationAlbum(artworkIcon, mutableColorScheme, width,
- height);
- } else {
- artwork = new ColorDrawable(Color.TRANSPARENT);
- }
-
- mMainExecutor.execute(() -> {
- // Bind the artwork drawable to media cover.
- ImageView mediaCover =
- mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
- // Rescale media cover
- Matrix coverMatrix = new Matrix(mediaCover.getImageMatrix());
- coverMatrix.postScale(REC_MEDIA_COVER_SCALE_FACTOR, REC_MEDIA_COVER_SCALE_FACTOR,
- 0.5f * width, 0.5f * height);
- mediaCover.setImageMatrix(coverMatrix);
- mediaCover.setImageDrawable(artwork);
-
- // Set up the app icon.
- ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
- appIconView.clearColorFilter();
- try {
- Drawable icon = mContext.getPackageManager()
- .getApplicationIcon(packageName);
- appIconView.setImageDrawable(icon);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find icon for package " + packageName, e);
- appIconView.setImageResource(R.drawable.ic_music_note);
- }
- Trace.endAsyncSection(traceName, traceCookie);
- });
- });
- }
-
// This method should be called from a background thread. WallpaperColors.fromBitmap takes a
// good amount of time. We do that work on the background executor to avoid stalling animations
// on the UI Thread.
@@ -1088,21 +961,6 @@ public class MediaControlPanel {
MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY);
}
- @VisibleForTesting
- protected LayerDrawable addGradientToRecommendationAlbum(Icon artworkIcon,
- ColorScheme mutableColorScheme, int width, int height) {
- // First try scaling rec card using bitmap drawable.
- // If returns null, set drawable bounds.
- Drawable albumArt = getScaledRecommendationCover(artworkIcon, width, height);
- if (albumArt == null) {
- albumArt = getScaledBackground(artworkIcon, width, height);
- }
- GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
- R.drawable.qs_media_rec_scrim).mutate();
- return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
- MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA);
- }
-
private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient,
ColorScheme mutableColorScheme, float startAlpha, float endAlpha) {
int startColor;
@@ -1465,258 +1323,6 @@ public class MediaControlPanel {
return controller;
}
- /** Bind this recommendation view based on the given data. */
- public void bindRecommendation(@NonNull SmartspaceMediaData data) {
- if (mRecommendationViewHolder == null) {
- return;
- }
-
- if (!data.isValid()) {
- Log.e(TAG, "Received an invalid recommendation list; returning");
- return;
- }
-
- if (Trace.isEnabled()) {
- Trace.traceBegin(Trace.TRACE_TAG_APP,
- "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
- }
-
- mRecommendationData = data;
- mPackageName = data.getPackageName();
- mInstanceId = data.getInstanceId();
-
- // Set up recommendation card's header.
- ApplicationInfo applicationInfo;
- try {
- applicationInfo = mContext.getPackageManager()
- .getApplicationInfo(data.getPackageName(), 0 /* flags */);
- mUid = applicationInfo.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Fail to get media recommendation's app info", e);
- Trace.endSection();
- return;
- }
-
- CharSequence appName = data.getAppName(mContext);
- if (appName == null) {
- Log.w(TAG, "Fail to get media recommendation's app name");
- Trace.endSection();
- return;
- }
-
- PackageManager packageManager = mContext.getPackageManager();
- // Set up media source app's logo.
- Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- fetchAndUpdateRecommendationColors(icon);
-
- // Set up media rec card's tap action if applicable.
- TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
- setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
- /* interactedSubcardRank */ -1);
- bindRecommendationContentDescription(data);
-
- List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
- List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
- List<SmartspaceAction> recommendations = data.getValidRecommendations();
-
- boolean hasTitle = false;
- boolean hasSubtitle = false;
- int fittedRecsNum = getNumberOfFittedRecommendations();
- for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
- SmartspaceAction recommendation = recommendations.get(itemIndex);
-
- // Set up media item cover.
- ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
-
- // Set up the media item's click listener if applicable.
- ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
- setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex);
- // Bubble up the long-click event to the card.
- mediaCoverContainer.setOnLongClickListener(v -> {
- if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
- View parent = (View) v.getParent();
- if (parent != null) {
- parent.performLongClick();
- }
- return true;
- });
-
- // Set up the accessibility label for the media item.
- String artistName = recommendation.getExtras()
- .getString(KEY_SMARTSPACE_ARTIST_NAME, "");
- if (artistName.isEmpty()) {
- mediaCoverImageView.setContentDescription(
- mContext.getString(
- R.string.controls_media_smartspace_rec_item_no_artist_description,
- recommendation.getTitle(), appName));
- } else {
- mediaCoverImageView.setContentDescription(
- mContext.getString(
- R.string.controls_media_smartspace_rec_item_description,
- recommendation.getTitle(), artistName, appName));
- }
-
- // Set up title
- CharSequence title = recommendation.getTitle();
- hasTitle |= !TextUtils.isEmpty(title);
- TextView titleView = mRecommendationViewHolder.getMediaTitles().get(itemIndex);
- titleView.setText(title);
-
- // Set up subtitle
- // It would look awkward to show a subtitle if we don't have a title.
- boolean shouldShowSubtitleText = !TextUtils.isEmpty(title);
- CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : "";
- hasSubtitle |= !TextUtils.isEmpty(subtitle);
- TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- subtitleView.setText(subtitle);
-
- // Set up progress bar
- SeekBar mediaProgressBar =
- mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
- TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- // show progress bar if the recommended album is played.
- Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
- if (progress == null || progress <= 0.0) {
- mediaProgressBar.setVisibility(View.GONE);
- mediaSubtitle.setVisibility(View.VISIBLE);
- } else {
- mediaProgressBar.setProgress((int) (progress * 100));
- mediaProgressBar.setVisibility(View.VISIBLE);
- mediaSubtitle.setVisibility(View.GONE);
- }
- }
- mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
-
- // If there's no subtitles and/or titles for any of the albums, hide those views.
- ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
- final boolean titlesVisible = hasTitle;
- final boolean subtitlesVisible = hasSubtitle;
- mRecommendationViewHolder.getMediaTitles().forEach((titleView) -> {
- setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible);
- setVisibleAndAlpha(collapsedSet, titleView.getId(), titlesVisible);
- });
- mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) -> {
- setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible);
- setVisibleAndAlpha(collapsedSet, subtitleView.getId(), subtitlesVisible);
- });
-
- // Media covers visibility.
- setMediaCoversVisibility(fittedRecsNum);
-
- // Guts
- Runnable onDismissClickedRunnable = () -> {
- closeGuts();
- mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
- data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
-
- Intent dismissIntent = data.getDismissIntent();
- if (dismissIntent == null) {
- Log.w(TAG, "Cannot create dismiss action click action: "
- + "extras missing dismiss_intent.");
- return;
- }
-
- if (dismissIntent.getComponent() != null
- && dismissIntent.getComponent().getClassName()
- .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) {
- // Dismiss the card Smartspace data through Smartspace trampoline activity.
- mContext.startActivity(dismissIntent);
- } else {
- mBroadcastSender.sendBroadcast(dismissIntent);
- }
- };
- bindGutsMenuCommon(
- /* isDismissible= */ true,
- appName.toString(),
- mRecommendationViewHolder.getGutsViewHolder(),
- onDismissClickedRunnable);
-
- mController = null;
- if (mMetadataAnimationHandler == null || !mMetadataAnimationHandler.isRunning()) {
- mMediaViewController.refreshState();
- }
- Trace.endSection();
- }
-
- private Unit updateRecommendationsVisibility() {
- int fittedRecsNum = getNumberOfFittedRecommendations();
- setMediaCoversVisibility(fittedRecsNum);
- return Unit.INSTANCE;
- }
-
- private void setMediaCoversVisibility(int fittedRecsNum) {
- ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
- List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
- // Hide media cover that cannot fit in the recommendation card.
- for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
- setVisibleAndAlpha(expandedSet, mediaCoverContainers.get(itemIndex).getId(),
- itemIndex < fittedRecsNum);
- setVisibleAndAlpha(collapsedSet, mediaCoverContainers.get(itemIndex).getId(),
- itemIndex < fittedRecsNum);
- }
- }
-
- @VisibleForTesting
- protected int getNumberOfFittedRecommendations() {
- Resources res = mContext.getResources();
- Configuration config = res.getConfiguration();
- int defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp);
- int recCoverWidth = res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
- + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2;
-
- // On landscape, media controls should take half of the screen width.
- int displayAvailableDpWidth = config.screenWidthDp;
- if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- displayAvailableDpWidth = displayAvailableDpWidth / 2;
- }
- int fittedNum;
- if (displayAvailableDpWidth > defaultDpWidth) {
- int recCoverDefaultWidth = res.getDimensionPixelSize(
- R.dimen.qs_media_rec_default_width);
- fittedNum = recCoverDefaultWidth / recCoverWidth;
- } else {
- int displayAvailableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- displayAvailableDpWidth, res.getDisplayMetrics());
- fittedNum = displayAvailableWidth / recCoverWidth;
- }
- return Math.min(fittedNum, NUM_REQUIRED_RECOMMENDATIONS);
- }
-
- private void fetchAndUpdateRecommendationColors(Drawable appIcon) {
- mBackgroundExecutor.execute(() -> {
- ColorScheme colorScheme = new ColorScheme(
- WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true);
- mMainExecutor.execute(() -> setRecommendationColors(colorScheme));
- });
- }
-
- private void setRecommendationColors(ColorScheme colorScheme) {
- if (mRecommendationViewHolder == null) {
- return;
- }
-
- int backgroundColor = MediaColorSchemesKt.surfaceFromScheme(colorScheme);
- int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
- int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
-
- mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
-
- mRecommendationViewHolder.getRecommendations()
- .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
- mRecommendationViewHolder.getMediaTitles().forEach(
- (title) -> title.setTextColor(textPrimaryColor));
- mRecommendationViewHolder.getMediaSubtitles().forEach(
- (subtitle) -> subtitle.setTextColor(textSecondaryColor));
- mRecommendationViewHolder.getMediaProgressBars().forEach(
- (progressBar) -> progressBar.setProgressTintList(
- ColorStateList.valueOf(textPrimaryColor)));
-
- mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
- }
-
private void bindGutsMenuCommon(
boolean isDismissible,
String appName,
@@ -1772,14 +1378,10 @@ public class MediaControlPanel {
public void closeGuts(boolean immediate) {
if (mMediaViewHolder != null) {
mMediaViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
- } else if (mRecommendationViewHolder != null) {
- mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.closeGuts(immediate);
if (mMediaViewHolder != null) {
bindPlayerContentDescription(mMediaData);
- } else if (mRecommendationViewHolder != null) {
- bindRecommendationContentDescription(mRecommendationData);
}
}
@@ -1790,14 +1392,10 @@ public class MediaControlPanel {
private void openGuts() {
if (mMediaViewHolder != null) {
mMediaViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
- } else if (mRecommendationViewHolder != null) {
- mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.openGuts();
if (mMediaViewHolder != null) {
bindPlayerContentDescription(mMediaData);
- } else if (mRecommendationViewHolder != null) {
- bindRecommendationContentDescription(mRecommendationData);
}
mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
}
@@ -1822,29 +1420,6 @@ public class MediaControlPanel {
}
/**
- * Scale artwork to fill the background of media covers in recommendation card.
- */
- @UiThread
- private Drawable getScaledRecommendationCover(Icon artworkIcon, int width, int height) {
- if (width == 0 || height == 0) {
- return null;
- }
- if (artworkIcon != null) {
- Bitmap bitmap;
- if (artworkIcon.getType() == Icon.TYPE_BITMAP
- || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
- Bitmap artworkBitmap = artworkIcon.getBitmap();
- if (artworkBitmap != null) {
- bitmap = Bitmap.createScaledBitmap(artworkIcon.getBitmap(), width,
- height, false);
- return new BitmapDrawable(mContext.getResources(), bitmap);
- }
- }
- }
- return null;
- }
-
- /**
* Get the current media controller
*
* @return the controller
@@ -1896,64 +1471,5 @@ public class MediaControlPanel {
set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : notVisibleValue);
set.setAlpha(actionId, visible ? 1.0f : 0.0f);
}
-
- private void setSmartspaceRecItemOnClickListener(
- @NonNull View view,
- @NonNull SmartspaceAction action,
- int interactedSubcardRank) {
- if (view == null || action == null || action.getIntent() == null
- || action.getIntent().getExtras() == null) {
- Log.e(TAG, "No tap action can be set up");
- return;
- }
-
- view.setOnClickListener(v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
-
- if (interactedSubcardRank == -1) {
- mLogger.logRecommendationCardTap(mPackageName, mInstanceId);
- } else {
- mLogger.logRecommendationItemTap(mPackageName, mInstanceId, interactedSubcardRank);
- }
-
- if (shouldSmartspaceRecItemOpenInForeground(action)) {
- // Request to unlock the device if the activity needs to be opened in foreground.
- mActivityStarter.postStartActivityDismissingKeyguard(
- action.getIntent(),
- 0 /* delay */,
- buildLaunchAnimatorController(
- mRecommendationViewHolder.getRecommendations()));
- } else {
- // Otherwise, open the activity in background directly.
- view.getContext().startActivity(action.getIntent());
- }
-
- // Automatically scroll to the active player once the media is loaded.
- mMediaCarouselController.setShouldScrollToKey(true);
- });
- }
-
- /** Returns if the Smartspace action will open the activity in foreground. */
- private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
- if (action == null || action.getIntent() == null
- || action.getIntent().getExtras() == null) {
- return false;
- }
-
- String intentString = action.getIntent().getExtras().getString(EXTRAS_SMARTSPACE_INTENT);
- if (intentString == null) {
- return false;
- }
-
- try {
- Intent wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
- return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false);
- } catch (URISyntaxException e) {
- Log.wtf(TAG, "Failed to create intent from URI: " + intentString);
- e.printStackTrace();
- }
-
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 69006c6107cc..ec7d3328a2fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -51,6 +51,7 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
@@ -289,6 +290,9 @@ constructor(
updateUserVisibility()
}
+ /** The expansion fraction of notification shade. */
+ var shadeExpandedFraction: Float = 0.0f
+
/**
* distance that the full shade transition takes in order for media to fully transition to the
* shade
@@ -868,7 +872,13 @@ constructor(
if (isCurrentlyInGuidedTransformation()) {
return false
}
- if (skipQqsOnExpansion) {
+ if (
+ skipQqsOnExpansion ||
+ (QSComposeFragment.isEnabled &&
+ desiredLocation == LOCATION_QQS &&
+ previousLocation == LOCATION_QS &&
+ shadeExpandedFraction == 0.0f)
+ ) {
return false
}
if (isHubTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index b687dce20b06..e87d5de56177 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -38,7 +38,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler
import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
-import com.android.systemui.media.controls.ui.binder.MediaRecommendationsViewBinder
import com.android.systemui.media.controls.ui.binder.SeekBarObserver
import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.media.controls.ui.view.GutsViewHolder
@@ -48,7 +47,6 @@ import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.hea
import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.labelLargeTF
import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.labelMediumTF
import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.titleMediumTF
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
@@ -90,15 +88,6 @@ constructor(
private val globalSettings: GlobalSettings,
) {
- /**
- * Indicating that the media view controller is for a notification-based player, session-based
- * player, or recommendation
- */
- enum class TYPE {
- PLAYER,
- RECOMMENDATION,
- }
-
companion object {
@JvmField val GUTS_ANIMATION_DURATION = 234L
}
@@ -115,7 +104,6 @@ constructor(
private var animationDuration: Long = 0
private var animateNextStateChange: Boolean = false
private val measurement = MeasurementOutput(0, 0)
- private var type: TYPE = TYPE.PLAYER
/** A map containing all viewStates for all locations of this mediaState */
private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf()
@@ -203,7 +191,6 @@ constructor(
private var isNextButtonAvailable = false
/** View holders for controller */
- var recommendationViewHolder: RecommendationViewHolder? = null
var mediaViewHolder: MediaViewHolder? = null
private lateinit var seekBarObserver: SeekBarObserver
@@ -242,6 +229,20 @@ constructor(
}
}
+ private val seekbarDescriptionListener =
+ object : SeekBarViewModel.ContentDescriptionListener {
+ override fun onContentDescriptionChanged(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ ) {
+ if (!SceneContainerFlag.isEnabled) return
+ seekBarObserver.updateContentDescription(
+ elapsedTimeDescription,
+ durationDescription,
+ )
+ }
+ }
+
/**
* Sets the listening state of the player.
*
@@ -363,6 +364,7 @@ constructor(
}
seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener)
seekBarViewModel.removeEnabledChangeListener(enabledChangeListener)
+ seekBarViewModel.removeContentDescriptionListener(seekbarDescriptionListener)
seekBarViewModel.onDestroy()
}
mediaHostStatesManager.removeController(this)
@@ -417,13 +419,9 @@ constructor(
/** Set the height of UMO background constraints. */
private fun setBackgroundHeights(height: Int) {
- val backgroundIds =
- if (type == TYPE.PLAYER) {
- MediaViewHolder.backgroundIds
- } else {
- setOf(RecommendationViewHolder.backgroundId)
- }
- backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height }
+ MediaViewHolder.backgroundIds.forEach { id ->
+ expandedLayout.getConstraint(id).layout.mHeight = height
+ }
}
/**
@@ -431,11 +429,7 @@ constructor(
* [TransitionViewState].
*/
private fun setGutsViewState(viewState: TransitionViewState) {
- val controlsIds =
- when (type) {
- TYPE.PLAYER -> MediaViewHolder.controlsIds
- TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
- }
+ val controlsIds = MediaViewHolder.controlsIds
val gutsIds = GutsViewHolder.ids
controlsIds.forEach { id ->
viewState.widgetStates.get(id)?.let { state ->
@@ -467,7 +461,6 @@ constructor(
squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight }
}
- // media player
calculateWidgetGroupAlphaForSquishiness(
MediaViewHolder.expandedBottomActionIds,
squishedViewState.measureHeight.toFloat(),
@@ -480,20 +473,6 @@ constructor(
squishedViewState,
squishFraction,
)
- // recommendation card
- val titlesTop =
- calculateWidgetGroupAlphaForSquishiness(
- RecommendationViewHolder.mediaTitlesAndSubtitlesIds,
- squishedViewState.measureHeight.toFloat(),
- squishedViewState,
- squishFraction,
- )
- calculateWidgetGroupAlphaForSquishiness(
- RecommendationViewHolder.mediaContainersIds,
- titlesTop,
- squishedViewState,
- squishFraction,
- )
return squishedViewState
}
@@ -661,10 +640,10 @@ constructor(
* 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) =
+ fun attach(transitionLayout: TransitionLayout) =
traceSection("MediaViewController#attach") {
- loadLayoutForType(type)
- logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
+ loadLayoutConstraints()
+ logger.logMediaLocation("attach", currentStartLocation, currentEndLocation)
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
if (currentEndLocation == MediaHierarchyManager.LOCATION_UNKNOWN) {
@@ -689,9 +668,10 @@ constructor(
seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar)
seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener)
seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
+ seekBarViewModel.setContentDescriptionListener(seekbarDescriptionListener)
val mediaCard = mediaViewHolder.player
- attach(mediaViewHolder.player, TYPE.PLAYER)
+ attach(mediaViewHolder.player)
val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView
turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
@@ -813,15 +793,6 @@ constructor(
}
}
- fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
- if (!SceneContainerFlag.isEnabled) return
- this.recommendationViewHolder = recommendationViewHolder
-
- attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
- recsConfigurationChangeListener =
- MediaRecommendationsViewBinder::updateRecommendationsVisibility
- }
-
fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
if (!SceneContainerFlag.isEnabled) return
seekBarViewModel.logSeek = onSeek
@@ -1026,20 +997,10 @@ constructor(
return result
}
- private fun loadLayoutForType(type: TYPE) {
- this.type = type
-
- // These XML resources contain ConstraintSets that will apply to this player type's layout
- when (type) {
- TYPE.PLAYER -> {
- collapsedLayout.load(context, R.xml.media_session_collapsed)
- expandedLayout.load(context, R.xml.media_session_expanded)
- }
- TYPE.RECOMMENDATION -> {
- collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
- expandedLayout.load(context, R.xml.media_recommendations_expanded)
- }
- }
+ private fun loadLayoutConstraints() {
+ // These XML resources contain ConstraintSets that will apply to this player's layout
+ collapsedLayout.load(context, R.xml.media_session_collapsed)
+ expandedLayout.load(context, R.xml.media_session_expanded)
readjustUIUpdateConstraints()
refreshState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
index f28edd638b10..2fc44ad3cce6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
@@ -42,8 +42,7 @@ class MediaViewModelCallback(
) {
oldItem.instanceId == newItem.instanceId
} else {
- oldItem is MediaCommonViewModel.MediaRecommendations &&
- newItem is MediaCommonViewModel.MediaRecommendations
+ false
}
}
@@ -56,11 +55,6 @@ class MediaViewModelCallback(
) {
oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi &&
oldItem.updateTime == newItem.updateTime
- } else if (
- oldItem is MediaCommonViewModel.MediaRecommendations &&
- newItem is MediaCommonViewModel.MediaRecommendations
- ) {
- oldItem.key == newItem.key && oldItem.loadingEnabled == newItem.loadingEnabled
} else {
false
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index 9cf4a7b3a007..68865d65139c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -127,6 +127,9 @@ class MediaCarouselScrollHandler(
scrollView.relativeScrollX = newRelativeScroll
}
+ /** Is scrolling disabled for the carousel */
+ var scrollingDisabled: Boolean = false
+
/** Does the dismiss currently show the setting cog? */
var showsSettingsButton: Boolean = false
@@ -270,6 +273,10 @@ class MediaCarouselScrollHandler(
}
private fun onTouch(motionEvent: MotionEvent): Boolean {
+ if (scrollingDisabled) {
+ return false
+ }
+
val isUp = motionEvent.action == MotionEvent.ACTION_UP
if (gestureDetector.onTouchEvent(motionEvent)) {
if (isUp) {
@@ -349,6 +356,10 @@ class MediaCarouselScrollHandler(
}
fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean {
+ if (scrollingDisabled) {
+ return false
+ }
+
val totalX = lastMotion.x - down.x
val currentTranslation = scrollView.getContentTranslation()
if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) {
@@ -405,6 +416,10 @@ class MediaCarouselScrollHandler(
}
private fun onFling(vX: Float, vY: Float): Boolean {
+ if (scrollingDisabled) {
+ return false
+ }
+
if (vX * vX < 0.5 * vY * vY) {
return false
}
@@ -575,6 +590,9 @@ class MediaCarouselScrollHandler(
* @param destIndex destination index to indicate where the scroll should end.
*/
fun scrollToPlayer(sourceIndex: Int = -1, destIndex: Int) {
+ if (scrollingDisabled) {
+ return
+ }
if (sourceIndex >= 0 && sourceIndex < mediaContent.childCount) {
scrollView.relativeScrollX = sourceIndex * playerWidthPlusPadding
}
@@ -596,6 +614,9 @@ class MediaCarouselScrollHandler(
* @param step A positive number means next, and negative means previous.
*/
fun scrollByStep(step: Int) {
+ if (scrollingDisabled) {
+ return
+ }
val destIndex = visibleMediaIndex + step
if (destIndex >= mediaContent.childCount || destIndex < 0) {
if (!showsSettingsButton) return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index a518349ea424..37af7642b105 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -295,7 +295,7 @@ class MediaHost(
changedListener?.invoke()
}
- override var disablePagination: Boolean = false
+ override var disableScrolling: Boolean = false
set(value) {
if (field == value) {
return
@@ -320,7 +320,7 @@ class MediaHost(
mediaHostState.visible = visible
mediaHostState.disappearParameters = disappearParameters.deepCopy()
mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded
- mediaHostState.disablePagination = disablePagination
+ mediaHostState.disableScrolling = disableScrolling
return mediaHostState
}
@@ -349,7 +349,7 @@ class MediaHost(
if (!disappearParameters.equals(other.disappearParameters)) {
return false
}
- if (disablePagination != other.disablePagination) {
+ if (disableScrolling != other.disableScrolling) {
return false
}
return true
@@ -363,7 +363,7 @@ class MediaHost(
result = 31 * result + showsOnlyActiveMedia.hashCode()
result = 31 * result + if (visible) 1 else 2
result = 31 * result + disappearParameters.hashCode()
- result = 31 * result + disablePagination.hashCode()
+ result = 31 * result + disableScrolling.hashCode()
return result
}
}
@@ -423,10 +423,11 @@ interface MediaHostState {
var disappearParameters: DisappearParameters
/**
- * Whether pagination should be disabled for this host, meaning that when there are multiple
- * media sessions, only the first one will appear.
+ * Whether scrolling should be disabled for this host, meaning that when there are multiple
+ * media sessions, it will not be possible to scroll between media sessions or swipe away the
+ * entire media carousel. The first media session will always be shown.
*/
- var disablePagination: Boolean
+ var disableScrolling: Boolean
/** Get a copy of this view state, deepcopying all appropriate members */
fun copy(): MediaHostState
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
index 0e8e595f5b06..848d8221e4db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
@@ -26,6 +26,10 @@ import android.widget.SeekBar
import android.widget.TextView
import androidx.constraintlayout.widget.Barrier
import com.android.internal.widget.CachingIconView
+import com.android.systemui.FontStyles.GSF_HEADLINE_SMALL
+import com.android.systemui.FontStyles.GSF_LABEL_LARGE
+import com.android.systemui.FontStyles.GSF_LABEL_MEDIUM
+import com.android.systemui.FontStyles.GSF_TITLE_MEDIUM
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -177,9 +181,9 @@ class MediaViewHolder constructor(itemView: View) {
R.id.touch_ripple_view,
)
- val headlineSmallTF: Typeface = Typeface.create("gsf-headline-small", Typeface.NORMAL)
- val titleMediumTF: Typeface = Typeface.create("gsf-title-medium", Typeface.NORMAL)
- val labelMediumTF: Typeface = Typeface.create("gsf-label-medium", Typeface.NORMAL)
- val labelLargeTF: Typeface = Typeface.create("gsf-label-large", Typeface.NORMAL)
+ val headlineSmallTF: Typeface = Typeface.create(GSF_HEADLINE_SMALL, Typeface.NORMAL)
+ val titleMediumTF: Typeface = Typeface.create(GSF_TITLE_MEDIUM, Typeface.NORMAL)
+ val labelMediumTF: Typeface = Typeface.create(GSF_LABEL_MEDIUM, Typeface.NORMAL)
+ val labelLargeTF: Typeface = Typeface.create(GSF_LABEL_LARGE, Typeface.NORMAL)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
deleted file mode 100644
index 2d028d0213ff..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.ui.view
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.SeekBar
-import android.widget.TextView
-import com.android.internal.widget.CachingIconView
-import com.android.systemui.media.controls.ui.drawable.IlluminationDrawable
-import com.android.systemui.res.R
-import com.android.systemui.util.animation.TransitionLayout
-
-private const val TAG = "RecommendationViewHolder"
-
-/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
-
- val recommendations = itemView as TransitionLayout
-
- // Recommendation screen
- val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
-
- 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 mediaAppIcons: List<CachingIconView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
- val mediaTitles: List<TextView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
- val mediaSubtitles: List<TextView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
- val mediaProgressBars: List<SeekBar> =
- mediaCoverContainers.map {
- it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
- // Media playback is in the direction of tape, not time, so it stays LTR
- layoutDirection = View.LAYOUT_DIRECTION_LTR
- }
- }
-
- val mediaCoverItems: List<ImageView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
- val gutsViewHolder = GutsViewHolder(itemView)
-
- init {
- (recommendations.background as IlluminationDrawable).let { background ->
- mediaCoverContainers.forEach { background.registerLightSource(it) }
- background.registerLightSource(gutsViewHolder.cancel)
- background.registerLightSource(gutsViewHolder.dismiss)
- background.registerLightSource(gutsViewHolder.settings)
- }
- }
-
- fun marquee(start: Boolean, delay: Long) {
- gutsViewHolder.marquee(start, delay, TAG)
- }
-
- companion object {
- /**
- * Creates a RecommendationViewHolder.
- *
- * @param inflater LayoutInflater to use to inflate the layout.
- * @param parent Parent of inflated view.
- */
- @JvmStatic
- fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
- val itemView =
- inflater.inflate(R.layout.media_recommendations, parent, 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.
- itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView)
- }
-
- // Res Ids for the control components on the recommendation view.
- val controlsIds =
- setOf(
- R.id.media_rec_title,
- R.id.media_cover,
- R.id.media_cover1_container,
- R.id.media_cover2_container,
- R.id.media_cover3_container,
- R.id.media_title,
- R.id.media_subtitle,
- )
-
- val mediaTitlesAndSubtitlesIds =
- setOf(
- R.id.media_title,
- R.id.media_subtitle,
- )
-
- val mediaContainersIds =
- setOf(
- R.id.media_cover1_container,
- R.id.media_cover2_container,
- R.id.media_cover3_container
- )
-
- val backgroundId = R.id.sizing_view
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index e5f1766fbb28..dfaee4434bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -49,7 +49,6 @@ constructor(
private val visualStabilityProvider: VisualStabilityProvider,
private val interactor: MediaCarouselInteractor,
private val controlInteractorFactory: MediaControlInteractorFactory,
- private val recommendationsViewModel: MediaRecommendationsViewModel,
private val logger: MediaUiEventLogger,
private val mediaLogger: MediaLogger,
) {
@@ -69,7 +68,7 @@ constructor(
when (commonModel) {
is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
is MediaCommonModel.MediaRecommendations ->
- add(toViewModel(commonModel))
+ return@forEach // TODO(b/382680767): remove
}
}
}
@@ -95,8 +94,6 @@ constructor(
private val mediaControlByInstanceId =
mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
- private var mediaRecs: MediaCommonViewModel.MediaRecommendations? = null
-
private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
private var allowReorder = false
@@ -149,37 +146,6 @@ constructor(
)
}
- private fun toViewModel(
- commonModel: MediaCommonModel.MediaRecommendations
- ): MediaCommonViewModel.MediaRecommendations {
- return mediaRecs?.copy(
- key = commonModel.recsLoadingModel.key,
- loadingEnabled = interactor.isRecommendationActive(),
- )
- ?: MediaCommonViewModel.MediaRecommendations(
- key = commonModel.recsLoadingModel.key,
- loadingEnabled = interactor.isRecommendationActive(),
- recsViewModel = recommendationsViewModel,
- onAdded = { commonViewModel ->
- mediaLogger.logMediaRecommendationCardAdded(
- commonModel.recsLoadingModel.key
- )
- onMediaRecommendationAddedOrUpdated(
- commonViewModel as MediaCommonViewModel.MediaRecommendations
- )
- },
- onRemoved = { immediatelyRemove ->
- onMediaRecommendationRemoved(commonModel, immediatelyRemove)
- },
- onUpdated = { commonViewModel ->
- onMediaRecommendationAddedOrUpdated(
- commonViewModel as MediaCommonViewModel.MediaRecommendations
- )
- },
- )
- .also { mediaRecs = it }
- }
-
private fun onMediaControlAddedOrUpdated(
commonViewModel: MediaCommonViewModel,
commonModel: MediaCommonModel.MediaControl,
@@ -197,32 +163,6 @@ constructor(
}
}
- private fun onMediaRecommendationAddedOrUpdated(
- commonViewModel: MediaCommonViewModel.MediaRecommendations
- ) {
- if (!interactor.isRecommendationActive()) {
- commonViewModel.onRemoved(true)
- }
- }
-
- private fun onMediaRecommendationRemoved(
- commonModel: MediaCommonModel.MediaRecommendations,
- immediatelyRemove: Boolean,
- ) {
- mediaLogger.logMediaRecommendationCardRemoved(commonModel.recsLoadingModel.key)
- if (immediatelyRemove || isReorderingAllowed()) {
- interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
- mediaRecs = null
- if (!immediatelyRemove) {
- // Although it wasn't requested, we were able to process the removal
- // immediately since reordering is allowed. So, notify hosts to update
- updateHostVisibility()
- }
- } else {
- modelsPendingRemoval.add(commonModel)
- }
- }
-
private fun isReorderingAllowed(): Boolean {
return visualStabilityProvider.isReorderingAllowed
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
index 52cb173b39cb..d493d57051f7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
@@ -35,13 +35,4 @@ sealed class MediaCommonViewModel {
val isMediaFromRec: Boolean = false,
val updateTime: Long = 0,
) : MediaCommonViewModel()
-
- data class MediaRecommendations(
- val key: String,
- val loadingEnabled: Boolean,
- val recsViewModel: MediaRecommendationsViewModel,
- override val onAdded: (MediaCommonViewModel) -> Unit,
- override val onRemoved: (Boolean) -> Unit,
- override val onUpdated: (MediaCommonViewModel) -> Unit,
- ) : MediaCommonViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
deleted file mode 100644
index 77add4035067..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
-
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
-import com.android.systemui.animation.Expandable
-
-/** Models UI state for media recommendation item */
-data class MediaRecViewModel(
- val contentDescription: CharSequence,
- val title: CharSequence = "",
- val subtitle: CharSequence = "",
- /** track progress [0 - 100] for the recommendation album. */
- val progress: Int = 0,
- val albumIcon: Icon? = null,
- val appIcon: Drawable,
- val onClicked: ((Expandable, Int) -> Unit),
-)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
deleted file mode 100644
index 90313ddc736e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.os.Process
-import android.util.Log
-import com.android.internal.logging.InstanceId
-import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
-import com.android.systemui.media.controls.shared.model.MediaRecModel
-import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
-import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.controller.MediaLocation
-import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION
-import com.android.systemui.media.controls.util.MediaDataUtils
-import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-
-/** Models UI state and handles user input for media recommendations */
-@SysUISingleton
-class MediaRecommendationsViewModel
-@Inject
-constructor(
- @Application private val applicationContext: Context,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val interactor: MediaRecommendationsInteractor,
- private val logger: MediaUiEventLogger,
-) {
-
- val mediaRecsCard: Flow<MediaRecsCardViewModel?> =
- interactor.recommendations
- .map { recsCard -> toRecsViewModel(recsCard) }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
-
- @MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN
-
- /**
- * Called whenever the recommendation has been expired or removed by the user. This method
- * removes the recommendation card entirely from the carousel.
- */
- private fun onMediaRecommendationsDismissed(
- key: String,
- uid: Int,
- packageName: String,
- dismissIntent: Intent?,
- instanceId: InstanceId?,
- ) {
- logger.logLongPressDismiss(uid, packageName, instanceId)
- interactor.removeMediaRecommendations(key, dismissIntent, GUTS_DISMISS_DELAY_MS_DURATION)
- }
-
- private fun onClicked(
- expandable: Expandable,
- intent: Intent?,
- packageName: String,
- instanceId: InstanceId?,
- index: Int,
- ) {
- if (intent == null || intent.extras == null) {
- Log.e(TAG, "No tap action can be set up")
- return
- }
-
- if (index == -1) {
- logger.logRecommendationCardTap(packageName, instanceId)
- } else {
- logger.logRecommendationItemTap(packageName, instanceId, index)
- }
-
- // set the package name of the player added by recommendation once the media is loaded.
- interactor.switchToMediaControl(packageName)
-
- interactor.startClickIntent(expandable, intent)
- }
-
- private suspend fun toRecsViewModel(model: MediaRecommendationsModel): MediaRecsCardViewModel? {
- if (!model.areRecommendationsValid) {
- Log.e(TAG, "Received an invalid recommendation list")
- return null
- }
- if (model.appName == null || model.uid == Process.INVALID_UID) {
- Log.w(TAG, "Fail to get media recommendation's app info")
- return null
- }
-
- val appIcon = getIconFromApp(model.packageName) ?: return null
-
- var areTitlesVisible = false
- var areSubtitlesVisible = false
- val mediaRecs =
- model.mediaRecs.map { mediaRecModel ->
- areTitlesVisible = areTitlesVisible || !mediaRecModel.title.isNullOrEmpty()
- areSubtitlesVisible = areSubtitlesVisible || !mediaRecModel.subtitle.isNullOrEmpty()
- val progress = MediaDataUtils.getDescriptionProgress(mediaRecModel.extras) ?: 0.0
- MediaRecViewModel(
- contentDescription =
- setUpMediaRecContentDescription(mediaRecModel, model.appName),
- title = mediaRecModel.title ?: "",
- subtitle = mediaRecModel.subtitle ?: "",
- progress = (progress * 100).toInt(),
- albumIcon = mediaRecModel.icon,
- appIcon = appIcon,
- onClicked = { expandable, index ->
- onClicked(
- expandable,
- mediaRecModel.intent,
- model.packageName,
- model.instanceId,
- index,
- )
- },
- )
- }
- // Subtitles should only be visible if titles are visible.
- areSubtitlesVisible = areTitlesVisible && areSubtitlesVisible
-
- return MediaRecsCardViewModel(
- contentDescription = { gutsVisible ->
- if (gutsVisible) {
- applicationContext.getString(
- R.string.controls_media_close_session,
- model.appName,
- )
- } else {
- applicationContext.getString(R.string.controls_media_smartspace_rec_header)
- }
- },
- onClicked = { expandable ->
- onClicked(
- expandable,
- model.dismissIntent,
- model.packageName,
- model.instanceId,
- index = -1,
- )
- },
- onLongClicked = {
- logger.logLongPressOpen(model.uid, model.packageName, model.instanceId)
- },
- mediaRecs = mediaRecs,
- areTitlesVisible = areTitlesVisible,
- areSubtitlesVisible = areSubtitlesVisible,
- gutsMenu = toGutsViewModel(model),
- onLocationChanged = { location = it },
- )
- }
-
- private fun toGutsViewModel(model: MediaRecommendationsModel): GutsViewModel {
- return GutsViewModel(
- gutsText =
- applicationContext.getString(R.string.controls_media_close_session, model.appName),
- onDismissClicked = {
- onMediaRecommendationsDismissed(
- model.key,
- model.uid,
- model.packageName,
- model.dismissIntent,
- model.instanceId,
- )
- },
- cancelTextBackground =
- applicationContext.getDrawable(R.drawable.qs_media_outline_button),
- onSettingsClicked = {
- logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
- interactor.startSettings()
- },
- )
- }
-
- private fun setUpMediaRecContentDescription(
- mediaRec: MediaRecModel,
- appName: CharSequence?,
- ): CharSequence {
- // Set up the accessibility label for the media item.
- val artistName = mediaRec.extras?.getString(KEY_SMARTSPACE_ARTIST_NAME, "")
- return if (artistName.isNullOrEmpty()) {
- applicationContext.getString(
- R.string.controls_media_smartspace_rec_item_no_artist_description,
- mediaRec.title,
- appName,
- )
- } else {
- applicationContext.getString(
- R.string.controls_media_smartspace_rec_item_description,
- mediaRec.title,
- artistName,
- appName,
- )
- }
- }
-
- private fun getIconFromApp(packageName: String): Drawable? {
- return try {
- applicationContext.packageManager.getApplicationIcon(packageName)
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Cannot find icon for package $packageName", e)
- null
- }
- }
-
- companion object {
- private const val TAG = "MediaRecommendationsViewModel"
- private const val KEY_SMARTSPACE_ARTIST_NAME = "artist_name"
- /**
- * Delay duration is based on [GUTS_ANIMATION_DURATION], it should have 100 ms increase in
- * order to let the animation end.
- */
- private const val GUTS_DISMISS_DELAY_MS_DURATION = 334L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
deleted file mode 100644
index f1f7dc2195d5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
-
-import com.android.systemui.animation.Expandable
-
-/** Models UI state for media recommendations card. */
-data class MediaRecsCardViewModel(
- val contentDescription: (Boolean) -> CharSequence,
- val onClicked: (Expandable) -> Unit,
- val onLongClicked: () -> Unit,
- val mediaRecs: List<MediaRecViewModel>,
- val areTitlesVisible: Boolean,
- val areSubtitlesVisible: Boolean,
- val gutsMenu: GutsViewModel,
- val onLocationChanged: (Int) -> Unit,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index a1f0cc33c065..78a8cf8e9432 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
@@ -39,8 +39,8 @@ import androidx.lifecycle.MutableLiveData
import com.android.systemui.Flags
import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.media.NotificationMediaManager
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.util.concurrency.RepeatableExecutor
import java.util.Locale
import javax.inject.Inject
@@ -104,19 +104,20 @@ constructor(
)
set(value) {
val enabledChanged = value.enabled != field.enabled
+ field = value
if (enabledChanged) {
enabledChangeListener?.onEnabledChanged(value.enabled)
}
+ _progress.postValue(value)
+
bgExecutor.execute {
val durationDescription = formatTimeContentDescription(value.duration)
val elapsedDescription =
value.elapsedTime?.let { formatTimeContentDescription(it) } ?: ""
- field =
- value.copy(
- durationDescription = durationDescription,
- elapsedTimeDescription = elapsedDescription,
- )
- _progress.postValue(field)
+ contentDescriptionListener?.onContentDescriptionChanged(
+ elapsedDescription,
+ durationDescription,
+ )
}
}
@@ -175,6 +176,7 @@ constructor(
private var scrubbingChangeListener: ScrubbingChangeListener? = null
private var enabledChangeListener: EnabledChangeListener? = null
+ private var contentDescriptionListener: ContentDescriptionListener? = null
/** Set to true when the user is touching the seek bar to change the position. */
private var scrubbing = false
@@ -394,6 +396,16 @@ constructor(
}
}
+ fun setContentDescriptionListener(listener: ContentDescriptionListener) {
+ contentDescriptionListener = listener
+ }
+
+ fun removeContentDescriptionListener(listener: ContentDescriptionListener) {
+ if (listener == contentDescriptionListener) {
+ contentDescriptionListener = null
+ }
+ }
+
/** returns a pair of whether seekbar is enabled and the duration of media. */
private fun getEnabledStateAndDuration(metadata: MediaMetadata?): Pair<Boolean, Int> {
val duration = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
@@ -468,6 +480,13 @@ constructor(
fun onEnabledChanged(enabled: Boolean)
}
+ interface ContentDescriptionListener {
+ fun onContentDescriptionChanged(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ )
+ }
+
private class SeekBarChangeListener(
val viewModel: SeekBarViewModel,
val falsingManager: FalsingManager,
@@ -639,7 +658,5 @@ constructor(
val duration: Int,
/** whether seekBar is listening to progress updates */
val listening: Boolean,
- val elapsedTimeDescription: CharSequence = "",
- val durationDescription: CharSequence = "",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
index c58ba377fb68..ac1672db9375 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
@@ -351,8 +351,9 @@ public abstract class MediaOutputAdapterBase extends RecyclerView.Adapter<Recycl
@VisibleForTesting
void showCustomEndSessionDialog(MediaDevice device) {
MediaSessionReleaseDialog mediaSessionReleaseDialog = new MediaSessionReleaseDialog(
- mContext, () -> transferOutput(device), mController.getColorButtonBackground(),
- mController.getColorItemContent());
+ mContext, () -> transferOutput(device),
+ mController.getColorSchemeLegacy().getColorButtonBackground(),
+ mController.getColorSchemeLegacy().getColorItemContent());
mediaSessionReleaseDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
index 300a3578bb8f..6ab4a52dc919 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
@@ -50,9 +50,11 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.media.flags.Flags;
import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
+import java.util.concurrent.Executor;
/**
* A RecyclerView adapter for the legacy UI media output dialog device list.
*/
@@ -61,14 +63,20 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int UNMUTE_DEFAULT_VOLUME = 2;
- private static final float DEVICE_DISABLED_ALPHA = 0.5f;
- private static final float DEVICE_ACTIVE_ALPHA = 1f;
+ @VisibleForTesting static final float DEVICE_DISABLED_ALPHA = 0.5f;
+ @VisibleForTesting static final float DEVICE_ACTIVE_ALPHA = 1f;
+ private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
View mHolderView;
- private boolean mIsInitVolumeFirstTime;
- public MediaOutputAdapterLegacy(MediaSwitchingController controller) {
+ public MediaOutputAdapterLegacy(
+ MediaSwitchingController controller,
+ @Main Executor mainExecutor,
+ @Background Executor backgroundExecutor
+ ) {
super(controller);
- mIsInitVolumeFirstTime = true;
+ mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
}
@Override
@@ -181,9 +189,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
mEndTouchArea.setVisibility(View.GONE);
mEndClickIcon.setVisibility(View.GONE);
mContainerLayout.setOnClickListener(null);
- mTitleText.setTextColor(mController.getColorItemContent());
- mSubTitleText.setTextColor(mController.getColorItemContent());
- mVolumeValueText.setTextColor(mController.getColorItemContent());
+ mTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent());
+ mSubTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent());
+ mVolumeValueText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent());
mIconAreaLayout.setBackground(null);
updateIconAreaClickListener(null);
updateSeekBarProgressColor();
@@ -193,14 +201,14 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
/** Binds a ViewHolder for a "Connect a device" item. */
void onBindPairNewDevice() {
- mTitleText.setTextColor(mController.getColorItemContent());
+ mTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent());
mCheckBox.setVisibility(View.GONE);
updateTitle(mContext.getText(R.string.media_output_dialog_pairing_new));
updateItemBackground(ConnectionState.DISCONNECTED);
final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
mTitleIcon.setImageDrawable(addDrawable);
- mTitleIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
+ mTitleIcon.setImageTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
}
@@ -251,10 +259,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
updateSeekbarProgressBackground();
}
}
- boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
if (showSeekBar) {
- initSeekbar(device, isCurrentSeekbarInvisible);
+ initSeekbar(device);
updateContainerContentA11yImportance(false /* isImportant */);
mSeekBar.setContentDescription(contentDescription);
} else {
@@ -264,9 +271,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
void updateGroupSeekBar(String contentDescription) {
updateSeekbarProgressBackground();
- boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
mSeekBar.setVisibility(View.VISIBLE);
- initGroupSeekbar(isCurrentSeekbarInvisible);
+ initGroupSeekbar();
updateContainerContentA11yImportance(false /* isImportant */);
mSeekBar.setContentDescription(contentDescription);
}
@@ -297,8 +303,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
protected void updateLoadingIndicator(ConnectionState connectionState) {
if (connectionState == ConnectionState.CONNECTING) {
mProgressBar.setVisibility(View.VISIBLE);
- mProgressBar.getIndeterminateDrawable().setTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
+ mProgressBar.getIndeterminateDrawable().setTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
} else {
mProgressBar.setVisibility(View.GONE);
}
@@ -318,8 +324,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
// Connected or connecting state has a darker background.
int backgroundColor = isConnected || isConnecting
- ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground();
+ ? mController.getColorSchemeLegacy().getColorConnectedItemBackground()
+ : mController.getColorSchemeLegacy().getColorItemBackground();
mItemLayout.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
}
@@ -332,13 +338,13 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
}
private void updateSeekBarProgressColor() {
- mSeekBar.setProgressTintList(
- ColorStateList.valueOf(mController.getColorSeekbarProgress()));
+ mSeekBar.setProgressTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorSeekbarProgress()));
final Drawable contrastDotDrawable =
((LayerDrawable) mSeekBar.getProgressDrawable()).findDrawableByLayerId(
R.id.contrast_dot);
- contrastDotDrawable.setTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
+ contrastDotDrawable.setTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
}
void updateSeekbarProgressBackground() {
@@ -354,31 +360,21 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
mActiveRadius, 0, 0});
}
- private void initializeSeekbarVolume(
- @Nullable MediaDevice device, int currentVolume,
- boolean isCurrentSeekbarInvisible) {
+ private void initializeSeekbarVolume(@Nullable MediaDevice device, int currentVolume) {
if (!isDragging()) {
if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
|| currentVolume == mLatestUpdateVolume)) {
// Update only if volume of device and value of volume bar doesn't match.
// Check if response volume match with the latest request, to ignore obsolete
// response
- if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
+ if (!mVolumeAnimator.isStarted()) {
if (currentVolume == 0) {
updateMutedVolumeIcon(device);
} else {
updateUnmutedVolumeIcon(device);
}
- } else {
- if (!mVolumeAnimator.isStarted()) {
- if (currentVolume == 0) {
- updateMutedVolumeIcon(device);
- } else {
- updateUnmutedVolumeIcon(device);
- }
- mSeekBar.setVolume(currentVolume);
- mLatestUpdateVolume = -1;
- }
+ mSeekBar.setVolume(currentVolume);
+ mLatestUpdateVolume = -1;
}
} else if (currentVolume == 0) {
mSeekBar.resetVolume();
@@ -388,12 +384,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
mLatestUpdateVolume = -1;
}
}
- if (mIsInitVolumeFirstTime) {
- mIsInitVolumeFirstTime = false;
- }
}
- void initSeekbar(@NonNull MediaDevice device, boolean isCurrentSeekbarInvisible) {
+ void initSeekbar(@NonNull MediaDevice device) {
SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
@Override
public int getVolume() {
@@ -422,7 +415,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
}
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
- initializeSeekbarVolume(device, currentVolume, isCurrentSeekbarInvisible);
+ initializeSeekbarVolume(device, currentVolume);
mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
device, volumeControl) {
@@ -435,7 +428,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
}
// Initializes the seekbar for a group of devices.
- void initGroupSeekbar(boolean isCurrentSeekbarInvisible) {
+ void initGroupSeekbar() {
SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
@Override
public int getVolume() {
@@ -462,7 +455,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
mSeekBar.setMaxVolume(mController.getSessionVolumeMax());
final int currentVolume = mController.getSessionVolume();
- initializeSeekbarVolume(null, currentVolume, isCurrentSeekbarInvisible);
+ initializeSeekbarVolume(null, currentVolume);
mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
null, volumeControl) {
@Override
@@ -503,9 +496,10 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
boolean isInputMediaDevice = device instanceof InputMediaDevice;
int id = getDrawableId(isInputMediaDevice, isMutedVolumeIcon);
mTitleIcon.setImageDrawable(mContext.getDrawable(id));
- mTitleIcon.setImageTintList(ColorStateList.valueOf(mController.getColorItemContent()));
- mIconAreaLayout.setBackgroundTintList(
- ColorStateList.valueOf(mController.getColorSeekbarProgress()));
+ mTitleIcon.setImageTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
+ mIconAreaLayout.setBackgroundTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorSeekbarProgress()));
}
@VisibleForTesting
@@ -534,8 +528,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
mStatusIcon.setVisibility(View.GONE);
} else {
mStatusIcon.setImageDrawable(deviceStatusIcon);
- mStatusIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
+ mStatusIcon.setImageTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
if (deviceStatusIcon instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) deviceStatusIcon).start();
}
@@ -585,9 +579,10 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
private void updateEndAreaWithIcon(View.OnClickListener clickListener,
@DrawableRes int iconDrawableId,
@StringRes int accessibilityStringId) {
- updateEndAreaColor(mController.getColorSeekbarProgress());
+ updateEndAreaColor(mController.getColorSchemeLegacy().getColorSeekbarProgress());
mEndClickIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
+ ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
mEndClickIcon.setOnClickListener(clickListener);
Drawable drawable = mContext.getDrawable(iconDrawableId);
mEndClickIcon.setImageDrawable(drawable);
@@ -600,8 +595,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
private void updateEndAreaForGroupCheckBox(@NonNull MediaDevice device,
@NonNull GroupStatus groupStatus) {
boolean isEnabled = isGroupCheckboxEnabled(groupStatus);
- updateEndAreaColor(groupStatus.selected() ? mController.getColorSeekbarProgress()
- : mController.getColorItemBackground());
+ updateEndAreaColor(groupStatus.selected()
+ ? mController.getColorSchemeLegacy().getColorSeekbarProgress()
+ : mController.getColorSchemeLegacy().getColorItemBackground());
mCheckBox.setContentDescription(mContext.getString(
groupStatus.selected() ? R.string.accessibility_remove_device_from_group
: R.string.accessibility_add_device_to_group));
@@ -611,7 +607,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
isEnabled ? (buttonView, isChecked) -> onGroupActionTriggered(
!groupStatus.selected(), device) : null);
mCheckBox.setEnabled(isEnabled);
- setCheckBoxColor(mCheckBox, mController.getColorItemContent());
+ setCheckBoxColor(mCheckBox, mController.getColorSchemeLegacy().getColorItemContent());
}
private void setCheckBoxColor(CheckBox checkBox, int color) {
@@ -714,15 +710,15 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
}
protected void setUpDeviceIcon(@NonNull MediaDevice device) {
- ThreadUtils.postOnBackgroundThread(() -> {
+ mBackgroundExecutor.execute(() -> {
Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
- ThreadUtils.postOnMainThread(() -> {
+ mMainExecutor.execute(() -> {
if (!TextUtils.equals(mDeviceId, device.getId())) {
return;
}
mTitleIcon.setImageIcon(icon);
- mTitleIcon.setImageTintList(
- ColorStateList.valueOf(mController.getColorItemContent()));
+ mTitleIcon.setImageTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
});
});
}
@@ -807,7 +803,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
}
void onBind(String groupDividerTitle) {
- mTitleText.setTextColor(mController.getColorItemContent());
+ mTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent());
mTitleText.setText(groupDividerTitle);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index d791361d555f..49d09cf64c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -40,7 +40,6 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -93,13 +92,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
private ImageView mAppResourceIcon;
private ImageView mBroadcastIcon;
private RecyclerView mDevicesRecyclerView;
- private LinearLayout mDeviceListLayout;
+ private ViewGroup mDeviceListLayout;
private LinearLayout mMediaMetadataSectionLayout;
private Button mDoneButton;
private Button mStopButton;
- private int mListMaxHeight;
- private int mItemHeight;
- private int mListPaddingTop;
private WallpaperColors mWallpaperColors;
private boolean mShouldLaunchLeBroadcastDialog;
private boolean mIsLeBroadcastCallbackRegistered;
@@ -109,17 +105,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
protected Executor mExecutor;
- private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
- ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
- int totalItemsHeight = mAdapter.getItemCount() * mItemHeight
- + mListPaddingTop;
- int correctHeight = Math.min(totalItemsHeight, mListMaxHeight);
- // Set max height for list
- if (correctHeight != params.height) {
- params.height = correctHeight;
- mDeviceListLayout.setLayoutParams(params);
- }
- };
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@@ -220,12 +205,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
mBroadcastSender = broadcastSender;
mMediaSwitchingController = mediaSwitchingController;
mLayoutManager = new LayoutManagerWrapper(mContext);
- mListMaxHeight = context.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_list_max_height);
- mItemHeight = context.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_list_item_height);
- mListPaddingTop = mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_list_padding_top);
mExecutor = Executors.newSingleThreadExecutor();
mIncludePlaybackAndAppMetadata = includePlaybackAndAppMetadata;
}
@@ -258,8 +237,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
mAppResourceIcon = mDialogView.requireViewById(R.id.app_source_icon);
mBroadcastIcon = mDialogView.requireViewById(R.id.broadcast_icon);
- mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
- mDeviceListLayoutListener);
// Init device list
mLayoutManager.setAutoMeasureEnabled(true);
mDevicesRecyclerView.setLayoutManager(mLayoutManager);
@@ -342,7 +319,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(icon.getBitmap());
colorSetUpdated = !wallpaperColors.equals(mWallpaperColors);
if (colorSetUpdated) {
- mMediaSwitchingController.setCurrentColorScheme(wallpaperColors, isDarkThemeOn);
+ mMediaSwitchingController.updateCurrentColorScheme(wallpaperColors,
+ isDarkThemeOn);
updateButtonBackgroundColorFilter();
updateDialogBackgroundColor();
}
@@ -359,7 +337,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
mAppResourceIcon.setVisibility(View.GONE);
} else if (appSourceIcon != null) {
Icon appIcon = appSourceIcon.toIcon(mContext);
- mAppResourceIcon.setColorFilter(mMediaSwitchingController.getColorItemContent());
+ mAppResourceIcon.setColorFilter(
+ mMediaSwitchingController.getColorSchemeLegacy().getColorItemContent());
mAppResourceIcon.setImageIcon(appIcon);
} else {
Drawable appIconDrawable = mMediaSwitchingController.getAppSourceIconFromPackage();
@@ -369,12 +348,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
mAppResourceIcon.setVisibility(View.GONE);
}
}
- if (mHeaderIcon.getVisibility() == View.VISIBLE) {
- final int size = getHeaderIconSize();
- final int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_header_icon_padding);
- mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
- }
if (!mIncludePlaybackAndAppMetadata) {
mHeaderTitle.setVisibility(View.GONE);
@@ -419,18 +392,19 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
private void updateButtonBackgroundColorFilter() {
ColorFilter buttonColorFilter =
new PorterDuffColorFilter(
- mMediaSwitchingController.getColorButtonBackground(),
+ mMediaSwitchingController.getColorSchemeLegacy().getColorButtonBackground(),
PorterDuff.Mode.SRC_IN);
mDoneButton.getBackground().setColorFilter(buttonColorFilter);
mStopButton.getBackground().setColorFilter(buttonColorFilter);
- mDoneButton.setTextColor(mMediaSwitchingController.getColorPositiveButtonText());
+ mDoneButton.setTextColor(
+ mMediaSwitchingController.getColorSchemeLegacy().getColorPositiveButtonText());
}
private void updateDialogBackgroundColor() {
- getDialogView()
- .getBackground()
- .setTint(mMediaSwitchingController.getColorDialogBackground());
- mDeviceListLayout.setBackgroundColor(mMediaSwitchingController.getColorDialogBackground());
+ getDialogView().getBackground().setTint(
+ mMediaSwitchingController.getColorSchemeLegacy().getColorDialogBackground());
+ mDeviceListLayout.setBackgroundColor(
+ mMediaSwitchingController.getColorSchemeLegacy().getColorDialogBackground());
}
public void handleLeBroadcastStarted() {
@@ -520,8 +494,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog
abstract IconCompat getHeaderIcon();
- abstract int getHeaderIconSize();
-
abstract CharSequence getHeaderText();
abstract CharSequence getHeaderSubtitle();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 9ade9e275ca1..791a61cc73ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -52,6 +52,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.google.zxing.WriterException;
+import java.util.concurrent.Executor;
+
/**
* Dialog for media output broadcast.
*/
@@ -239,13 +241,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
Context context,
boolean aboveStatusbar,
BroadcastSender broadcastSender,
- MediaSwitchingController mediaSwitchingController) {
+ MediaSwitchingController mediaSwitchingController,
+ Executor mainExecutor,
+ Executor backgroundExecutor) {
super(
context,
broadcastSender,
mediaSwitchingController, /* includePlaybackAndAppMetadata */
true);
- mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mainExecutor,
+ backgroundExecutor);
// TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class
// that extends MediaOutputBaseDialog
if (!aboveStatusbar) {
@@ -295,12 +300,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
@Override
- int getHeaderIconSize() {
- return mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_header_album_icon_size);
- }
-
- @Override
CharSequence getHeaderText() {
return mMediaSwitchingController.getHeaderTitle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 2e7e66f5b384..81c85a6ad22d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -20,6 +20,9 @@ import android.content.Context
import android.view.View
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
import javax.inject.Inject
/** Manager to create and show a [MediaOutputBroadcastDialog]. */
@@ -29,7 +32,9 @@ constructor(
private val context: Context,
private val broadcastSender: BroadcastSender,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory
+ private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory,
+ @Main private val mainExecutor: Executor,
+ @Background private val backgroundExecutor: Executor,
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -47,7 +52,14 @@ constructor(
/* token */ null,
)
val dialog =
- MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
+ MediaOutputBroadcastDialog(
+ context,
+ aboveStatusBar,
+ broadcastSender,
+ controller,
+ mainExecutor,
+ backgroundExecutor,
+ )
mediaOutputBroadcastDialog = dialog
// Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt
new file mode 100644
index 000000000000..7f0fa463811b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog
+
+import android.content.Context
+import com.android.settingslib.Utils
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.res.R
+
+abstract class MediaOutputColorSchemeLegacy {
+ companion object Factory {
+ @JvmStatic
+ fun fromSystemColors(context: Context): MediaOutputColorSchemeLegacy {
+ return MediaOutputColorSchemeLegacySystem(context)
+ }
+
+ @JvmStatic
+ fun fromDynamicColors(
+ colorScheme: ColorScheme,
+ isDarkTheme: Boolean,
+ ): MediaOutputColorSchemeLegacy {
+ return MediaOutputColorSchemeLegacyDynamic(colorScheme, isDarkTheme)
+ }
+ }
+
+ abstract fun getColorConnectedItemBackground(): Int
+
+ abstract fun getColorPositiveButtonText(): Int
+
+ abstract fun getColorDialogBackground(): Int
+
+ abstract fun getColorItemContent(): Int
+
+ abstract fun getColorSeekbarProgress(): Int
+
+ abstract fun getColorButtonBackground(): Int
+
+ abstract fun getColorItemBackground(): Int
+}
+
+class MediaOutputColorSchemeLegacySystem(private val mContext: Context) :
+ MediaOutputColorSchemeLegacy() {
+
+ override fun getColorConnectedItemBackground() =
+ Utils.getColorStateListDefaultColor(
+ mContext,
+ R.color.media_dialog_connected_item_background,
+ )
+
+ override fun getColorPositiveButtonText() =
+ Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_solid_button_text)
+
+ override fun getColorDialogBackground() =
+ Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_background)
+
+ override fun getColorItemContent() =
+ Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_item_main_content)
+
+ override fun getColorSeekbarProgress() =
+ Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_seekbar_progress)
+
+ override fun getColorButtonBackground() =
+ Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_button_background)
+
+ override fun getColorItemBackground() =
+ Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_item_background)
+}
+
+class MediaOutputColorSchemeLegacyDynamic(colorScheme: ColorScheme, isDarkTheme: Boolean) :
+ MediaOutputColorSchemeLegacy() {
+ private var mColorItemContent: Int
+ private var mColorSeekbarProgress: Int
+ private var mColorButtonBackground: Int
+ private var mColorItemBackground: Int
+ private var mColorConnectedItemBackground: Int
+ private var mColorPositiveButtonText: Int
+ private var mColorDialogBackground: Int
+
+ init {
+ if (isDarkTheme) {
+ mColorItemContent = colorScheme.accent1.s100 // A1-100
+ mColorSeekbarProgress = colorScheme.accent2.s600 // A2-600
+ mColorButtonBackground = colorScheme.accent1.s300 // A1-300
+ mColorItemBackground = colorScheme.neutral2.s800 // N2-800
+ mColorConnectedItemBackground = colorScheme.accent2.s800 // A2-800
+ mColorPositiveButtonText = colorScheme.accent2.s800 // A2-800
+ mColorDialogBackground = colorScheme.neutral1.s900 // N1-900
+ } else {
+ mColorItemContent = colorScheme.accent1.s800 // A1-800
+ mColorSeekbarProgress = colorScheme.accent1.s300 // A1-300
+ mColorButtonBackground = colorScheme.accent1.s600 // A1-600
+ mColorItemBackground = colorScheme.accent2.s50 // A2-50
+ mColorConnectedItemBackground = colorScheme.accent1.s100 // A1-100
+ mColorPositiveButtonText = colorScheme.neutral1.s50 // N1-50
+ mColorDialogBackground = colorScheme.backgroundColor
+ }
+ }
+
+ override fun getColorConnectedItemBackground() = mColorConnectedItemBackground
+
+ override fun getColorPositiveButtonText() = mColorPositiveButtonText
+
+ override fun getColorDialogBackground() = mColorDialogBackground
+
+ override fun getColorItemContent() = mColorItemContent
+
+ override fun getColorSeekbarProgress() = mColorSeekbarProgress
+
+ override fun getColorButtonBackground() = mColorButtonBackground
+
+ override fun getColorItemBackground() = mColorItemBackground
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 2e602be4556e..163ff248b9df 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -34,6 +34,8 @@ import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.res.R;
+import java.util.concurrent.Executor;
+
/**
* Dialog for media output transferring.
*/
@@ -49,11 +51,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
MediaSwitchingController mediaSwitchingController,
DialogTransitionAnimator dialogTransitionAnimator,
UiEventLogger uiEventLogger,
+ Executor mainExecutor,
+ Executor backgroundExecutor,
boolean includePlaybackAndAppMetadata) {
super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata);
mDialogTransitionAnimator = dialogTransitionAnimator;
mUiEventLogger = uiEventLogger;
- mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController);
+ mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mainExecutor,
+ backgroundExecutor);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
@@ -76,12 +81,6 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
}
@Override
- int getHeaderIconSize() {
- return mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_header_album_icon_size);
- }
-
- @Override
CharSequence getHeaderText() {
return mMediaSwitchingController.getHeaderTitle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 4e9451a838ad..d3a81a53b6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -25,6 +25,9 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
import javax.inject.Inject
/** Manager to create and show a [MediaOutputDialog]. */
@@ -37,6 +40,9 @@ constructor(
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory,
) {
+ @Inject @Main lateinit var mainExecutor: Executor
+ @Inject @Background lateinit var backgroundExecutor: Executor
+
companion object {
const val INTERACTION_JANK_TAG = "media_output"
var mediaOutputDialog: MediaOutputDialog? = null
@@ -51,7 +57,7 @@ constructor(
aboveStatusBar: Boolean,
view: View? = null,
userHandle: UserHandle? = null,
- token: MediaSession.Token? = null
+ token: MediaSession.Token? = null,
) {
createAndShowWithController(
packageName,
@@ -62,8 +68,8 @@ constructor(
it,
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- )
+ INTERACTION_JANK_TAG,
+ ),
)
},
userHandle = userHandle,
@@ -128,15 +134,14 @@ constructor(
controller,
dialogTransitionAnimator,
uiEventLogger,
- includePlaybackAndAppMetadata
+ mainExecutor,
+ backgroundExecutor,
+ includePlaybackAndAppMetadata,
)
// Show the dialog.
if (dialogTransitionAnimatorController != null) {
- dialogTransitionAnimator.show(
- mediaOutputDialog,
- dialogTransitionAnimatorController,
- )
+ dialogTransitionAnimator.show(mediaOutputDialog, dialogTransitionAnimatorController)
} else {
mediaOutputDialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 1f2f571496bd..d0c6a3e6a3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -66,6 +66,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -78,11 +79,12 @@ import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.InputRouteManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaItem.MediaItemType;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
@@ -114,6 +116,8 @@ import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;
+import javax.inject.Inject;
+
/**
* Controller for a dialog that allows users to switch media output and input devices, control
* volume, connect to new devices, etc.
@@ -141,7 +145,7 @@ public class MediaSwitchingController
@VisibleForTesting
final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
- private final List<MediaItem> mOutputMediaItemList = new CopyOnWriteArrayList<>();
+ private final OutputMediaItemListProxy mOutputMediaItemListProxy;
private final List<MediaItem> mInputMediaItemList = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
@@ -149,7 +153,8 @@ public class MediaSwitchingController
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
private final MediaSession.Token mToken;
-
+ @Inject @Main Executor mMainExecutor;
+ @Inject @Background Executor mBackgroundExecutor;
@VisibleForTesting
boolean mIsRefreshing = false;
@VisibleForTesting
@@ -163,17 +168,10 @@ public class MediaSwitchingController
@VisibleForTesting
MediaOutputMetricLogger mMetricLogger;
private int mCurrentState;
-
- private int mColorItemContent;
- private int mColorSeekbarProgress;
- private int mColorButtonBackground;
- private int mColorItemBackground;
- private int mColorConnectedItemBackground;
- private int mColorPositiveButtonText;
- private int mColorDialogBackground;
private FeatureFlags mFeatureFlags;
private UserTracker mUserTracker;
private VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor;
+ @NonNull private MediaOutputColorSchemeLegacy mMediaOutputColorSchemeLegacy;
public enum BroadcastNotifyDialog {
ACTION_FIRST_LAUNCH,
@@ -228,22 +226,10 @@ public class MediaSwitchingController
InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
+ mOutputMediaItemListProxy = new OutputMediaItemListProxy(context);
mDialogTransitionAnimator = dialogTransitionAnimator;
mNearbyMediaDevicesManager = nearbyMediaDevicesManager;
- mColorItemContent = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_item_main_content);
- mColorSeekbarProgress = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_seekbar_progress);
- mColorButtonBackground = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_button_background);
- mColorItemBackground = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_item_background);
- mColorConnectedItemBackground = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_connected_item_background);
- mColorPositiveButtonText = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_solid_button_text);
- mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_background);
+ mMediaOutputColorSchemeLegacy = MediaOutputColorSchemeLegacy.fromSystemColors(mContext);
if (enableInputRouting()) {
mInputRouteManager = new InputRouteManager(mContext, audioManager);
@@ -260,7 +246,7 @@ public class MediaSwitchingController
protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mOutputMediaItemList.clear();
+ mOutputMediaItemListProxy.clear();
}
mNearbyDeviceInfoMap.clear();
if (mNearbyMediaDevicesManager != null) {
@@ -306,7 +292,7 @@ public class MediaSwitchingController
mLocalMediaManager.stopScan();
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mOutputMediaItemList.clear();
+ mOutputMediaItemListProxy.clear();
}
if (mNearbyMediaDevicesManager != null) {
mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this);
@@ -322,7 +308,8 @@ public class MediaSwitchingController
}
private MediaController getMediaController() {
- if (mToken != null && Flags.usePlaybackInfoForRoutingControls()) {
+ if (mToken != null
+ && com.android.settingslib.media.flags.Flags.usePlaybackInfoForRoutingControls()) {
return new MediaController(mContext, mToken);
} else {
for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
@@ -348,7 +335,7 @@ public class MediaSwitchingController
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
- boolean isListEmpty = mOutputMediaItemList.isEmpty();
+ boolean isListEmpty = mOutputMediaItemListProxy.isEmpty();
if (isListEmpty || !mIsRefreshing) {
buildMediaItems(devices);
mCallback.onDeviceListChanged();
@@ -362,11 +349,12 @@ public class MediaSwitchingController
}
@Override
- public void onSelectedDeviceStateChanged(MediaDevice device,
- @LocalMediaManager.MediaDeviceState int state) {
+ public void onSelectedDeviceStateChanged(
+ MediaDevice device, @LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged();
mMetricLogger.logOutputItemSuccess(
- device.toString(), new ArrayList<>(mOutputMediaItemList));
+ device.toString(),
+ new ArrayList<>(mOutputMediaItemListProxy.getOutputMediaItemList()));
}
@Override
@@ -377,7 +365,8 @@ public class MediaSwitchingController
@Override
public void onRequestFailed(int reason) {
mCallback.onRouteChanged();
- mMetricLogger.logOutputItemFailure(new ArrayList<>(mOutputMediaItemList), reason);
+ mMetricLogger.logOutputItemFailure(
+ new ArrayList<>(mOutputMediaItemListProxy.getOutputMediaItemList()), reason);
}
/**
@@ -396,7 +385,7 @@ public class MediaSwitchingController
}
try {
synchronized (mMediaDevicesLock) {
- mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
+ mOutputMediaItemListProxy.removeMutingExpectedDevices();
}
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
} catch (Exception e) {
@@ -568,26 +557,15 @@ public class MediaSwitchingController
return null;
}
- void setCurrentColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) {
- ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors,
+ void updateCurrentColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) {
+ ColorScheme currentColorScheme = new ColorScheme(wallpaperColors,
isDarkTheme);
- if (isDarkTheme) {
- mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100
- mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600
- mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300
- mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800
- mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800
- mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800
- mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900
- } else {
- mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800
- mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300
- mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600
- mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50
- mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100
- mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50
- mColorDialogBackground = mCurrentColorScheme.getBackgroundColor();
- }
+ mMediaOutputColorSchemeLegacy = MediaOutputColorSchemeLegacy.fromDynamicColors(
+ currentColorScheme, isDarkTheme);
+ }
+
+ MediaOutputColorSchemeLegacy getColorSchemeLegacy() {
+ return mMediaOutputColorSchemeLegacy;
}
public void refreshDataSetIfNeeded() {
@@ -598,49 +576,37 @@ public class MediaSwitchingController
}
}
- public int getColorConnectedItemBackground() {
- return mColorConnectedItemBackground;
- }
-
- public int getColorPositiveButtonText() {
- return mColorPositiveButtonText;
- }
-
- public int getColorDialogBackground() {
- return mColorDialogBackground;
- }
-
- public int getColorItemContent() {
- return mColorItemContent;
- }
-
- public int getColorSeekbarProgress() {
- return mColorSeekbarProgress;
- }
-
- public int getColorButtonBackground() {
- return mColorButtonBackground;
- }
-
- public int getColorItemBackground() {
- return mColorItemBackground;
- }
-
private void buildMediaItems(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
- List<MediaItem> updatedMediaItems = buildMediaItems(mOutputMediaItemList, devices);
- mOutputMediaItemList.clear();
- mOutputMediaItemList.addAll(updatedMediaItems);
- }
- }
-
- protected List<MediaItem> buildMediaItems(List<MediaItem> oldMediaItems,
- List<MediaDevice> devices) {
- synchronized (mMediaDevicesLock) {
if (!mLocalMediaManager.isPreferenceRouteListingExist()) {
attachRangeInfo(devices);
Collections.sort(devices, Comparator.naturalOrder());
}
+ if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) {
+ // For the first time building list, to make sure the top device is the connected
+ // device.
+ boolean needToHandleMutingExpectedDevice =
+ hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote();
+ final MediaDevice connectedMediaDevice =
+ needToHandleMutingExpectedDevice ? null : getCurrentConnectedMediaDevice();
+ mOutputMediaItemListProxy.updateMediaDevices(
+ devices,
+ getSelectedMediaDevice(),
+ connectedMediaDevice,
+ needToHandleMutingExpectedDevice,
+ getConnectNewDeviceItem());
+ } else {
+ List<MediaItem> updatedMediaItems =
+ buildMediaItems(
+ mOutputMediaItemListProxy.getOutputMediaItemList(), devices);
+ mOutputMediaItemListProxy.clearAndAddAll(updatedMediaItems);
+ }
+ }
+ }
+
+ protected List<MediaItem> buildMediaItems(
+ List<MediaItem> oldMediaItems, List<MediaDevice> devices) {
+ synchronized (mMediaDevicesLock) {
// For the first time building list, to make sure the top device is the connected
// device.
boolean needToHandleMutingExpectedDevice =
@@ -648,10 +614,6 @@ public class MediaSwitchingController
final MediaDevice connectedMediaDevice =
needToHandleMutingExpectedDevice ? null
: getCurrentConnectedMediaDevice();
-
- Set<String> selectedDevicesIds = getSelectedMediaDevice().stream()
- .map(MediaDevice::getId)
- .collect(Collectors.toSet());
if (oldMediaItems.isEmpty()) {
if (connectedMediaDevice == null) {
if (DEBUG) {
@@ -660,14 +622,12 @@ public class MediaSwitchingController
return categorizeMediaItemsLocked(
/* connectedMediaDevice */ null,
devices,
- selectedDevicesIds,
needToHandleMutingExpectedDevice);
} else {
// selected device exist
return categorizeMediaItemsLocked(
connectedMediaDevice,
devices,
- selectedDevicesIds,
/* needToHandleMutingExpectedDevice */ false);
}
}
@@ -701,18 +661,27 @@ public class MediaSwitchingController
devices.removeAll(targetMediaDevices);
targetMediaDevices.addAll(devices);
}
- List<MediaItem> finalMediaItems = new ArrayList<>();
- boolean shouldAddFirstSeenSelectedDevice =
- com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping();
- for (MediaDevice targetMediaDevice : targetMediaDevices) {
- if (shouldAddFirstSeenSelectedDevice
- && selectedDevicesIds.contains(targetMediaDevice.getId())) {
- finalMediaItems.add(MediaItem.createDeviceMediaItem(
- targetMediaDevice, /* isFirstDeviceInGroup */ true));
- shouldAddFirstSeenSelectedDevice = false;
- } else {
- finalMediaItems.add(MediaItem.createDeviceMediaItem(
- targetMediaDevice, /* isFirstDeviceInGroup */ false));
+ List<MediaItem> finalMediaItems = targetMediaDevices.stream()
+ .map(MediaItem::createDeviceMediaItem)
+ .collect(Collectors.toList());
+
+ boolean shouldAddFirstSeenSelectedDevice = Flags.enableOutputSwitcherDeviceGrouping();
+
+ if (shouldAddFirstSeenSelectedDevice) {
+ finalMediaItems.clear();
+ Set<String> selectedDevicesIds = getSelectedMediaDevice().stream()
+ .map(MediaDevice::getId)
+ .collect(Collectors.toSet());
+ for (MediaDevice targetMediaDevice : targetMediaDevices) {
+ if (shouldAddFirstSeenSelectedDevice
+ && selectedDevicesIds.contains(targetMediaDevice.getId())) {
+ finalMediaItems.add(MediaItem.createDeviceMediaItem(
+ targetMediaDevice, /* isFirstDeviceInGroup */ true));
+ shouldAddFirstSeenSelectedDevice = false;
+ } else {
+ finalMediaItems.add(MediaItem.createDeviceMediaItem(
+ targetMediaDevice, /* isFirstDeviceInGroup */ false));
+ }
}
}
dividerItems.forEach(finalMediaItems::add);
@@ -722,7 +691,7 @@ public class MediaSwitchingController
}
private boolean enableInputRouting() {
- return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl();
+ return Flags.enableAudioInputDeviceRoutingAndVolumeControl();
}
private void buildInputMediaItems(List<MediaDevice> devices) {
@@ -739,16 +708,18 @@ public class MediaSwitchingController
* list.
*/
@GuardedBy("mMediaDevicesLock")
- private List<MediaItem> categorizeMediaItemsLocked(MediaDevice connectedMediaDevice,
+ private List<MediaItem> categorizeMediaItemsLocked(
+ MediaDevice connectedMediaDevice,
List<MediaDevice> devices,
- Set<String> selectedDevicesIds,
boolean needToHandleMutingExpectedDevice) {
List<MediaItem> finalMediaItems = new ArrayList<>();
+ Set<String> selectedDevicesIds = getSelectedMediaDevice().stream()
+ .map(MediaDevice::getId)
+ .collect(Collectors.toSet());
if (connectedMediaDevice != null) {
selectedDevicesIds.add(connectedMediaDevice.getId());
}
- boolean groupSelectedDevices =
- com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping();
+ boolean groupSelectedDevices = Flags.enableOutputSwitcherDeviceGrouping();
int nextSelectedItemIndex = 0;
boolean suggestedDeviceAdded = false;
boolean displayGroupAdded = false;
@@ -796,6 +767,14 @@ public class MediaSwitchingController
}
private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) {
+ MediaItem connectNewDeviceItem = getConnectNewDeviceItem();
+ if (connectNewDeviceItem != null) {
+ mediaItems.add(connectNewDeviceItem);
+ }
+ }
+
+ @Nullable
+ private MediaItem getConnectNewDeviceItem() {
boolean isSelectedDeviceNotAGroup = getSelectedMediaDevice().size() == 1;
if (enableInputRouting()) {
// When input routing is enabled, there are expected to be at least 2 total selected
@@ -804,9 +783,9 @@ public class MediaSwitchingController
}
// Attach "Connect a device" item only when current output is not remote and not a group
- if (!isCurrentConnectedDeviceRemote() && isSelectedDeviceNotAGroup) {
- mediaItems.add(MediaItem.createPairNewDeviceMediaItem());
- }
+ return (!isCurrentConnectedDeviceRemote() && isSelectedDeviceNotAGroup)
+ ? MediaItem.createPairNewDeviceMediaItem()
+ : null;
}
private void attachRangeInfo(List<MediaDevice> devices) {
@@ -895,13 +874,13 @@ public class MediaSwitchingController
mediaItems.add(
MediaItem.createGroupDividerMediaItem(
mContext.getString(R.string.media_output_group_title)));
- mediaItems.addAll(mOutputMediaItemList);
+ mediaItems.addAll(mOutputMediaItemListProxy.getOutputMediaItemList());
}
public List<MediaItem> getMediaItemList() {
// If input routing is not enabled, only return output media items.
if (!enableInputRouting()) {
- return mOutputMediaItemList;
+ return mOutputMediaItemListProxy.getOutputMediaItemList();
}
// If input routing is enabled, return both output and input media items.
@@ -915,6 +894,11 @@ public class MediaSwitchingController
return mLocalMediaManager.getCurrentConnectedDevice();
}
+ @VisibleForTesting
+ void clearMediaItemList() {
+ mOutputMediaItemListProxy.clear();
+ }
+
boolean addDeviceToPlayMedia(MediaDevice device) {
mMetricLogger.logInteractionExpansion(device);
return mLocalMediaManager.addDeviceToPlayMedia(device);
@@ -1007,7 +991,7 @@ public class MediaSwitchingController
public boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
- for (MediaItem mediaItem : mOutputMediaItemList) {
+ for (MediaItem mediaItem : mOutputMediaItemListProxy.getOutputMediaItemList()) {
if (mediaItem.getMediaDevice().isPresent()
&& mediaItem.getMediaDevice().get().getState()
== LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
@@ -1047,8 +1031,11 @@ public class MediaSwitchingController
startActivity(launchIntent, controller);
}
- void launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender,
- BroadcastNotifyDialog action, final DialogInterface.OnClickListener listener) {
+ void launchLeBroadcastNotifyDialog(
+ View mediaOutputDialog,
+ BroadcastSender broadcastSender,
+ BroadcastNotifyDialog action,
+ final DialogInterface.OnClickListener listener) {
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
switch (action) {
case ACTION_FIRST_LAUNCH:
@@ -1091,7 +1078,7 @@ public class MediaSwitchingController
mVolumePanelGlobalStateInteractor,
mUserTracker);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
- broadcastSender, controller);
+ broadcastSender, controller, mMainExecutor, mBackgroundExecutor);
dialog.show();
}
@@ -1278,8 +1265,8 @@ public class MediaSwitchingController
return !sourceList.isEmpty();
}
- boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink,
- BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
+ boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
LocalBluetoothLeBroadcastAssistant assistant =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java
new file mode 100644
index 000000000000..45ca2c6ee8e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.media.flags.Flags;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.res.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
+
+/** A proxy of holding the list of Output Switcher's output media items. */
+public class OutputMediaItemListProxy {
+ private final Context mContext;
+ private final List<MediaItem> mOutputMediaItemList;
+
+ // Use separated lists to hold different media items and create the list of output media items
+ // by using those separated lists and group dividers.
+ private final List<MediaItem> mSelectedMediaItems;
+ private final List<MediaItem> mSuggestedMediaItems;
+ private final List<MediaItem> mSpeakersAndDisplaysMediaItems;
+ @Nullable private MediaItem mConnectNewDeviceMediaItem;
+
+ public OutputMediaItemListProxy(Context context) {
+ mContext = context;
+ mOutputMediaItemList = new CopyOnWriteArrayList<>();
+ mSelectedMediaItems = new CopyOnWriteArrayList<>();
+ mSuggestedMediaItems = new CopyOnWriteArrayList<>();
+ mSpeakersAndDisplaysMediaItems = new CopyOnWriteArrayList<>();
+ }
+
+ /** Returns the list of output media items. */
+ public List<MediaItem> getOutputMediaItemList() {
+ if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) {
+ if (isEmpty() && !mOutputMediaItemList.isEmpty()) {
+ // Ensures mOutputMediaItemList is empty when all individual media item lists are
+ // empty, preventing unexpected state issues.
+ mOutputMediaItemList.clear();
+ } else if (!isEmpty() && mOutputMediaItemList.isEmpty()) {
+ // When any individual media item list is modified, the cached mOutputMediaItemList
+ // is emptied. On the next request for the output media item list, a fresh list is
+ // created and stored in the cache.
+ mOutputMediaItemList.addAll(createOutputMediaItemList());
+ }
+ }
+ return mOutputMediaItemList;
+ }
+
+ private List<MediaItem> createOutputMediaItemList() {
+ List<MediaItem> finalMediaItems = new CopyOnWriteArrayList<>();
+ finalMediaItems.addAll(mSelectedMediaItems);
+ if (!mSuggestedMediaItems.isEmpty()) {
+ finalMediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(
+ R.string.media_output_group_title_suggested_device)));
+ finalMediaItems.addAll(mSuggestedMediaItems);
+ }
+ if (!mSpeakersAndDisplaysMediaItems.isEmpty()) {
+ finalMediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays)));
+ finalMediaItems.addAll(mSpeakersAndDisplaysMediaItems);
+ }
+ if (mConnectNewDeviceMediaItem != null) {
+ finalMediaItems.add(mConnectNewDeviceMediaItem);
+ }
+ return finalMediaItems;
+ }
+
+ /** Updates the list of output media items with a given list of media devices. */
+ public void updateMediaDevices(
+ List<MediaDevice> devices,
+ List<MediaDevice> selectedDevices,
+ @Nullable MediaDevice connectedMediaDevice,
+ boolean needToHandleMutingExpectedDevice,
+ @Nullable MediaItem connectNewDeviceMediaItem) {
+ Set<String> selectedOrConnectedMediaDeviceIds =
+ selectedDevices.stream().map(MediaDevice::getId).collect(Collectors.toSet());
+ if (connectedMediaDevice != null) {
+ selectedOrConnectedMediaDeviceIds.add(connectedMediaDevice.getId());
+ }
+
+ List<MediaItem> selectedMediaItems = new ArrayList<>();
+ List<MediaItem> suggestedMediaItems = new ArrayList<>();
+ List<MediaItem> speakersAndDisplaysMediaItems = new ArrayList<>();
+ Map<String, MediaItem> deviceIdToMediaItemMap = new HashMap<>();
+ buildMediaItems(
+ devices,
+ selectedOrConnectedMediaDeviceIds,
+ needToHandleMutingExpectedDevice,
+ selectedMediaItems,
+ suggestedMediaItems,
+ speakersAndDisplaysMediaItems,
+ deviceIdToMediaItemMap);
+
+ List<MediaItem> updatedSelectedMediaItems = new CopyOnWriteArrayList<>();
+ List<MediaItem> updatedSuggestedMediaItems = new CopyOnWriteArrayList<>();
+ List<MediaItem> updatedSpeakersAndDisplaysMediaItems = new CopyOnWriteArrayList<>();
+ if (isEmpty()) {
+ updatedSelectedMediaItems.addAll(selectedMediaItems);
+ updatedSuggestedMediaItems.addAll(suggestedMediaItems);
+ updatedSpeakersAndDisplaysMediaItems.addAll(speakersAndDisplaysMediaItems);
+ } else {
+ Set<String> updatedDeviceIds = new HashSet<>();
+ // Preserve the existing media item order while updating with the latest device
+ // information. Some items may retain their original group (suggested, speakers and
+ // displays) to maintain this order.
+ updateMediaItems(
+ mSelectedMediaItems,
+ updatedSelectedMediaItems,
+ deviceIdToMediaItemMap,
+ updatedDeviceIds);
+ updateMediaItems(
+ mSuggestedMediaItems,
+ updatedSuggestedMediaItems,
+ deviceIdToMediaItemMap,
+ updatedDeviceIds);
+ updateMediaItems(
+ mSpeakersAndDisplaysMediaItems,
+ updatedSpeakersAndDisplaysMediaItems,
+ deviceIdToMediaItemMap,
+ updatedDeviceIds);
+
+ // Append new media items that are not already in the existing lists to the output list.
+ List<MediaItem> remainingMediaItems = new ArrayList<>();
+ remainingMediaItems.addAll(
+ getRemainingMediaItems(selectedMediaItems, updatedDeviceIds));
+ remainingMediaItems.addAll(
+ getRemainingMediaItems(suggestedMediaItems, updatedDeviceIds));
+ remainingMediaItems.addAll(
+ getRemainingMediaItems(speakersAndDisplaysMediaItems, updatedDeviceIds));
+ updatedSpeakersAndDisplaysMediaItems.addAll(remainingMediaItems);
+ }
+
+ if (Flags.enableOutputSwitcherDeviceGrouping() && !updatedSelectedMediaItems.isEmpty()) {
+ MediaItem selectedMediaItem = updatedSelectedMediaItems.get(0);
+ Optional<MediaDevice> mediaDeviceOptional = selectedMediaItem.getMediaDevice();
+ if (mediaDeviceOptional.isPresent()) {
+ MediaItem updatedMediaItem =
+ MediaItem.createDeviceMediaItem(
+ mediaDeviceOptional.get(), /* isFirstDeviceInGroup= */ true);
+ updatedSelectedMediaItems.remove(0);
+ updatedSelectedMediaItems.add(0, updatedMediaItem);
+ }
+ }
+
+ mSelectedMediaItems.clear();
+ mSelectedMediaItems.addAll(updatedSelectedMediaItems);
+ mSuggestedMediaItems.clear();
+ mSuggestedMediaItems.addAll(updatedSuggestedMediaItems);
+ mSpeakersAndDisplaysMediaItems.clear();
+ mSpeakersAndDisplaysMediaItems.addAll(updatedSpeakersAndDisplaysMediaItems);
+ mConnectNewDeviceMediaItem = connectNewDeviceMediaItem;
+
+ // The cached mOutputMediaItemList is cleared upon any update to individual media item
+ // lists. This ensures getOutputMediaItemList() computes and caches a fresh list on the next
+ // invocation.
+ mOutputMediaItemList.clear();
+ }
+
+ /** Updates the list of output media items with the given list. */
+ public void clearAndAddAll(List<MediaItem> updatedMediaItems) {
+ mOutputMediaItemList.clear();
+ mOutputMediaItemList.addAll(updatedMediaItems);
+ }
+
+ /** Removes the media items with muting expected devices. */
+ public void removeMutingExpectedDevices() {
+ if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) {
+ mSelectedMediaItems.removeIf((MediaItem::isMutingExpectedDevice));
+ mSuggestedMediaItems.removeIf((MediaItem::isMutingExpectedDevice));
+ mSpeakersAndDisplaysMediaItems.removeIf((MediaItem::isMutingExpectedDevice));
+ if (mConnectNewDeviceMediaItem != null
+ && mConnectNewDeviceMediaItem.isMutingExpectedDevice()) {
+ mConnectNewDeviceMediaItem = null;
+ }
+ }
+ mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
+ }
+
+ /** Clears the output media item list. */
+ public void clear() {
+ if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) {
+ mSelectedMediaItems.clear();
+ mSuggestedMediaItems.clear();
+ mSpeakersAndDisplaysMediaItems.clear();
+ mConnectNewDeviceMediaItem = null;
+ }
+ mOutputMediaItemList.clear();
+ }
+
+ /** Returns whether the output media item list is empty. */
+ public boolean isEmpty() {
+ if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) {
+ return mSelectedMediaItems.isEmpty()
+ && mSuggestedMediaItems.isEmpty()
+ && mSpeakersAndDisplaysMediaItems.isEmpty()
+ && (mConnectNewDeviceMediaItem == null);
+ } else {
+ return mOutputMediaItemList.isEmpty();
+ }
+ }
+
+ private void buildMediaItems(
+ List<MediaDevice> devices,
+ Set<String> selectedOrConnectedMediaDeviceIds,
+ boolean needToHandleMutingExpectedDevice,
+ List<MediaItem> selectedMediaItems,
+ List<MediaItem> suggestedMediaItems,
+ List<MediaItem> speakersAndDisplaysMediaItems,
+ Map<String, MediaItem> deviceIdToMediaItemMap) {
+ for (MediaDevice device : devices) {
+ String deviceId = device.getId();
+ MediaItem mediaItem = MediaItem.createDeviceMediaItem(device);
+ if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) {
+ selectedMediaItems.add(0, mediaItem);
+ } else if (!needToHandleMutingExpectedDevice
+ && selectedOrConnectedMediaDeviceIds.contains(device.getId())) {
+ if (Flags.enableOutputSwitcherDeviceGrouping()) {
+ selectedMediaItems.add(mediaItem);
+ } else {
+ selectedMediaItems.add(0, mediaItem);
+ }
+ } else if (device.isSuggestedDevice()) {
+ suggestedMediaItems.add(mediaItem);
+ } else {
+ speakersAndDisplaysMediaItems.add(mediaItem);
+ }
+ deviceIdToMediaItemMap.put(deviceId, mediaItem);
+ }
+ }
+
+ /** Returns a list of media items that remains the same order as the existing media items. */
+ private void updateMediaItems(
+ List<MediaItem> existingMediaItems,
+ List<MediaItem> updatedMediaItems,
+ Map<String, MediaItem> deviceIdToMediaItemMap,
+ Set<String> updatedDeviceIds) {
+ List<String> existingDeviceIds = getDeviceIds(existingMediaItems);
+ for (String deviceId : existingDeviceIds) {
+ MediaItem mediaItem = deviceIdToMediaItemMap.get(deviceId);
+ if (mediaItem != null) {
+ updatedMediaItems.add(mediaItem);
+ updatedDeviceIds.add(deviceId);
+ }
+ }
+ }
+
+ /**
+ * Returns media items from the input list that are not associated with the given device IDs.
+ */
+ private List<MediaItem> getRemainingMediaItems(
+ List<MediaItem> mediaItems, Set<String> deviceIds) {
+ List<MediaItem> remainingMediaItems = new ArrayList<>();
+ for (MediaItem item : mediaItems) {
+ Optional<MediaDevice> mediaDeviceOptional = item.getMediaDevice();
+ if (mediaDeviceOptional.isPresent()) {
+ String deviceId = mediaDeviceOptional.get().getId();
+ if (!deviceIds.contains(deviceId)) {
+ remainingMediaItems.add(item);
+ }
+ }
+ }
+ return remainingMediaItems;
+ }
+
+ /** Returns a list of media device IDs for the given list of media items. */
+ private List<String> getDeviceIds(List<MediaItem> mediaItems) {
+ List<String> deviceIds = new ArrayList<>();
+ for (MediaItem item : mediaItems) {
+ if (item != null && item.getMediaDevice().isPresent()) {
+ deviceIds.add(item.getMediaDevice().get().getId());
+ }
+ }
+ return deviceIds;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt
new file mode 100644
index 000000000000..afed141644ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.domain.interactor
+
+import com.android.systemui.media.remedia.domain.model.MediaSessionModel
+
+/**
+ * Defines interface for classes that can provide business logic in the domain of the media controls
+ * element.
+ */
+interface MediaInteractor {
+
+ /** The list of sessions. Needs to be backed by a compose snapshot state. */
+ val sessions: List<MediaSessionModel>
+
+ /** Seek to [to], in milliseconds on the media session with the given [sessionKey]. */
+ fun seek(sessionKey: Any, to: Long)
+
+ /** Hide the representation of the media session with the given [sessionKey]. */
+ fun hide(sessionKey: Any)
+
+ /** Open media settings. */
+ fun openMediaSettings()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt
new file mode 100644
index 000000000000..02e4d7a966c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.domain.model
+
+import com.android.systemui.common.shared.model.Icon
+
+sealed interface MediaActionModel {
+ data class Action(val icon: Icon, val onClick: (() -> Unit)?) : MediaActionModel
+
+ data object ReserveSpace : MediaActionModel
+
+ data object None : MediaActionModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt
new file mode 100644
index 000000000000..d581ae3e4e40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.domain.model
+
+import com.android.systemui.common.shared.model.Icon
+
+data class MediaOutputDeviceModel(val name: String, val icon: Icon, val isInProgress: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt
new file mode 100644
index 000000000000..e64ce73226f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.domain.model
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.graphics.ImageBitmap
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout
+import com.android.systemui.media.remedia.shared.model.MediaColorScheme
+import com.android.systemui.media.remedia.shared.model.MediaSessionState
+
+/** Data model representing a media session. */
+@Stable
+interface MediaSessionModel {
+ /** Unique identifier. */
+ val key: Any
+
+ val appName: String
+
+ val appIcon: Icon
+
+ val background: ImageBitmap?
+
+ val colorScheme: MediaColorScheme
+
+ val title: String
+
+ val subtitle: String
+
+ val onClick: () -> Unit
+
+ /**
+ * Whether the session is currently active. Under some UIs, only currently active session should
+ * be shown.
+ */
+ val isActive: Boolean
+
+ /** Whether the session can be hidden/dismissed by the user. */
+ val canBeHidden: Boolean
+
+ /**
+ * Whether the session currently supports scrubbing (e.g. moving to a different position iin the
+ * playback.
+ */
+ val canBeScrubbed: Boolean
+
+ val state: MediaSessionState
+
+ /** The position of the playback within the current track. */
+ val positionMs: Long
+
+ /** The total duration of the current track. */
+ val durationMs: Long
+
+ val outputDevice: MediaOutputDeviceModel
+
+ /** How to lay out the action buttons. */
+ val actionButtonLayout: MediaCardActionButtonLayout
+ val playPauseAction: MediaActionModel
+ val leftAction: MediaActionModel
+ val rightAction: MediaActionModel
+ val additionalActions: List<MediaActionModel.Action>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt
new file mode 100644
index 000000000000..c3ce5035accc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.shared.model
+
+enum class MediaActionState {
+ Enabled,
+ Disabled,
+ Hidden,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt
new file mode 100644
index 000000000000..554fb1f2fc43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.shared.model
+
+enum class MediaCardActionButtonLayout {
+ /** Shows the play/pause button and left/right buttons in privileged positions on the card */
+ WithPlayPause,
+ /** Shows all action buttons along the bottom row. */
+ SecondaryActionsOnly,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt
new file mode 100644
index 000000000000..8dba170f0928
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.shared.model
+
+import androidx.compose.ui.graphics.Color
+
+data class MediaColorScheme(val primary: Color, val onPrimary: Color)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt
new file mode 100644
index 000000000000..40d55af9ba3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.shared.model
+
+sealed interface MediaSessionState {
+ data object Playing : MediaSessionState
+
+ data object Paused : MediaSessionState
+
+ data object Buffering : MediaSessionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt
new file mode 100644
index 000000000000..8df916fe6969
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.compose
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerScope
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.thenIf
+import kotlinx.coroutines.launch
+
+/** State for a [DismissibleHorizontalPager] */
+class DismissibleHorizontalPagerState(
+ val isDismissible: Boolean,
+ val isScrollingEnabled: Boolean,
+ val pagerState: PagerState,
+ val offset: Animatable<Float, AnimationVector1D>,
+)
+
+/**
+ * Returns a remembered [DismissibleHorizontalPagerState] that starts at [initialPage] and has
+ * [pageCount] total pages.
+ */
+@Composable
+fun rememberDismissibleHorizontalPagerState(
+ isDismissible: Boolean = true,
+ isScrollingEnabled: Boolean = true,
+ initialPage: Int = 0,
+ pageCount: () -> Int,
+): DismissibleHorizontalPagerState {
+ val pagerState = rememberPagerState(initialPage = initialPage, pageCount = pageCount)
+ val offset = remember { Animatable(0f) }
+
+ return remember(isDismissible, isScrollingEnabled, pagerState, offset) {
+ DismissibleHorizontalPagerState(
+ isDismissible = isDismissible,
+ isScrollingEnabled = isScrollingEnabled,
+ pagerState = pagerState,
+ offset = offset,
+ )
+ }
+}
+
+/**
+ * A [HorizontalPager] that can be swiped-away to dismiss by the user when swiped farther left or
+ * right once fully scrolled to the left-most or right-most page, respectively.
+ */
+@Composable
+fun DismissibleHorizontalPager(
+ state: DismissibleHorizontalPagerState,
+ onDismissed: () -> Unit,
+ modifier: Modifier = Modifier,
+ key: ((Int) -> Any)? = null,
+ pageSpacing: Dp = 0.dp,
+ isFalseTouchDetected: Boolean,
+ indicator: @Composable BoxScope.() -> Unit,
+ pageContent: @Composable PagerScope.(page: Int) -> Unit,
+) {
+ val scope = rememberCoroutineScope()
+
+ val nestedScrollConnection = remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ return if (state.offset.value > 0f && available.x < 0f) {
+ scope.launch { state.offset.snapTo(state.offset.value + available.x) }
+ Offset(available.x, 0f)
+ } else if (state.offset.value < 0f && available.x > 0f) {
+ scope.launch { state.offset.snapTo(state.offset.value + available.x) }
+ Offset(available.x, 0f)
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource,
+ ): Offset {
+ return if (available.x > 0f) {
+ scope.launch { state.offset.snapTo(state.offset.value + available.x) }
+ Offset(available.x, 0f)
+ } else if (available.x < 0f) {
+ scope.launch { state.offset.snapTo(state.offset.value + available.x) }
+ Offset(available.x, 0f)
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ scope.launch {
+ state.offset.animateTo(
+ if (state.offset.value >= state.pagerState.layoutInfo.pageSize / 2f) {
+ state.pagerState.layoutInfo.pageSize * 2f
+ } else if (
+ state.offset.value <= -state.pagerState.layoutInfo.pageSize / 2f
+ ) {
+ -state.pagerState.layoutInfo.pageSize * 2f
+ } else {
+ 0f
+ }
+ )
+ if (state.offset.value != 0f) {
+ onDismissed()
+ }
+ }
+ return super.onPostFling(consumed, available)
+ }
+ }
+ }
+
+ Box(modifier = modifier) {
+ HorizontalPager(
+ state = state.pagerState,
+ userScrollEnabled = state.isScrollingEnabled && !isFalseTouchDetected,
+ key = key,
+ pageSpacing = pageSpacing,
+ pageContent = pageContent,
+ modifier =
+ Modifier.thenIf(state.isDismissible) {
+ Modifier.nestedScroll(nestedScrollConnection).graphicsLayer {
+ translationX = state.offset.value
+ }
+ },
+ )
+
+ indicator()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
new file mode 100644
index 000000000000..f07238895aa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
@@ -0,0 +1,1273 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.android.systemui.media.remedia.ui.compose
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.hoverable
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderColors
+import androidx.compose.material3.SliderDefaults.colors
+import androidx.compose.material3.SliderState
+import androidx.compose.material3.Text
+import androidx.compose.material3.ripple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.center
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastRoundToInt
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformIconButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.transitions
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.PagerDots
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout
+import com.android.systemui.media.remedia.shared.model.MediaColorScheme
+import com.android.systemui.media.remedia.shared.model.MediaSessionState
+import com.android.systemui.media.remedia.ui.viewmodel.MediaCardGutsViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaCardViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaCarouselVisibility
+import com.android.systemui.media.remedia.ui.viewmodel.MediaNavigationViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaOutputSwitcherChipViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaPlayPauseActionViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaSecondaryActionViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Renders a media controls UI element.
+ *
+ * This composable supports a multitude of presentation styles/layouts controlled by the
+ * [presentationStyle] parameter. If the card carousel can be swiped away to dismiss by the user,
+ * the [onDismissed] callback will be invoked when/if that happens.
+ */
+@Composable
+fun Media(
+ viewModelFactory: MediaViewModel.Factory,
+ presentationStyle: MediaPresentationStyle,
+ behavior: MediaUiBehavior,
+ onDismissed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+ val viewModel: MediaViewModel =
+ rememberViewModel("Media.viewModel") {
+ viewModelFactory.create(
+ context = context,
+ carouselVisibility = behavior.carouselVisibility,
+ )
+ }
+
+ CardCarousel(
+ viewModel = viewModel,
+ presentationStyle = presentationStyle,
+ behavior = behavior,
+ onDismissed = onDismissed,
+ modifier = modifier,
+ )
+}
+
+/**
+ * Renders a media controls carousel of cards.
+ *
+ * This composable supports a multitude of presentation styles/layouts controlled by the
+ * [presentationStyle] parameter. The behavior is controlled by [behavior]. If
+ * [MediaUiBehavior.isCarouselDismissible] is `true`, the [onDismissed] callback will be invoked
+ * when/if that happens.
+ */
+@Composable
+private fun CardCarousel(
+ viewModel: MediaViewModel,
+ presentationStyle: MediaPresentationStyle,
+ behavior: MediaUiBehavior,
+ onDismissed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ AnimatedVisibility(visible = viewModel.isCarouselVisible, modifier = modifier) {
+ CardCarouselContent(
+ viewModel = viewModel,
+ presentationStyle = presentationStyle,
+ behavior = behavior,
+ onDismissed = onDismissed,
+ )
+ }
+}
+
+@Composable
+private fun CardCarouselContent(
+ viewModel: MediaViewModel,
+ presentationStyle: MediaPresentationStyle,
+ behavior: MediaUiBehavior,
+ onDismissed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val pagerState =
+ rememberDismissibleHorizontalPagerState(
+ isDismissible = behavior.isCarouselDismissible,
+ isScrollingEnabled = behavior.isCarouselScrollingEnabled,
+ ) {
+ viewModel.cards.size
+ }
+ var isFalseTouchDetected: Boolean by
+ remember(behavior.isCarouselScrollFalseTouch) { mutableStateOf(false) }
+
+ val roundedCornerShape = RoundedCornerShape(32.dp)
+
+ LaunchedEffect(pagerState.pagerState.currentPage) {
+ viewModel.onCardSelected(pagerState.pagerState.currentPage)
+ }
+
+ DismissibleHorizontalPager(
+ state = pagerState,
+ onDismissed = onDismissed,
+ pageSpacing = 8.dp,
+ key = { index -> viewModel.cards[index].key },
+ indicator = {
+ if (pagerState.pagerState.pageCount > 1) {
+ PagerDots(
+ pagerState = pagerState.pagerState,
+ activeColor = Color(0xffdee0ff),
+ nonActiveColor = Color(0xffa7a9ca),
+ dotSize = 6.dp,
+ spaceSize = 6.dp,
+ modifier =
+ Modifier.align(Alignment.BottomCenter).padding(8.dp).graphicsLayer {
+ translationX = pagerState.offset.value
+ },
+ )
+ }
+ },
+ isFalseTouchDetected = isFalseTouchDetected,
+ modifier =
+ modifier.padding(8.dp).clip(roundedCornerShape).pointerInput(behavior) {
+ if (behavior.isCarouselScrollFalseTouch != null) {
+ awaitEachGesture {
+ awaitFirstDown(false, PointerEventPass.Initial)
+ isFalseTouchDetected = behavior.isCarouselScrollFalseTouch.invoke()
+ }
+ }
+ },
+ ) { index ->
+ Card(
+ viewModel = viewModel.cards[index],
+ presentationStyle = presentationStyle,
+ modifier = Modifier.clip(roundedCornerShape),
+ )
+ }
+}
+
+/** Renders the UI of a single media card. */
+@Composable
+private fun Card(
+ viewModel: MediaCardViewModel,
+ presentationStyle: MediaPresentationStyle,
+ modifier: Modifier = Modifier,
+) {
+ val stlState =
+ rememberMutableSceneTransitionLayoutState(
+ initialScene = presentationStyle.toScene(),
+ transitions = Media.Transitions,
+ )
+
+ // Each time the presentation style changes, animate to the corresponding scene.
+ LaunchedEffect(presentationStyle) {
+ stlState.setTargetScene(targetScene = presentationStyle.toScene(), animationScope = this)
+ }
+
+ Box(modifier) {
+ if (stlState.currentScene != Media.Scenes.Compact) {
+ CardBackground(image = viewModel.background, modifier = Modifier.matchParentSize())
+ }
+
+ key(stlState) {
+ SceneTransitionLayout(state = stlState) {
+ scene(Media.Scenes.Default) {
+ CardForeground(viewModel = viewModel, threeRows = true, fillHeight = false)
+ }
+
+ scene(Media.Scenes.Compressed) {
+ CardForeground(viewModel = viewModel, threeRows = false, fillHeight = false)
+ }
+
+ scene(Media.Scenes.Compact) { CompactCardForeground(viewModel = viewModel) }
+ }
+ }
+ }
+}
+
+@Composable
+private fun rememberAnimatedColorScheme(colorScheme: MediaColorScheme): AnimatedColorScheme {
+ val animatedPrimary by animateColorAsState(targetValue = colorScheme.primary)
+ val animatedOnPrimary by animateColorAsState(targetValue = colorScheme.onPrimary)
+
+ return remember {
+ object : AnimatedColorScheme {
+ override val primary: Color
+ get() = animatedPrimary
+
+ override val onPrimary: Color
+ get() = animatedOnPrimary
+ }
+ }
+}
+
+/**
+ * Renders the foreground of a card, including all UI content and the internal "guts".
+ *
+ * If [threeRows] is `true`, the layout will be organized as three horizontal rows; if `false`, two
+ * rows will be used, resulting in a more compact layout.
+ *
+ * If [fillHeight] is `true`, the card will grow vertically to fill all available space in its
+ * parent. If not, it'll only be as tall as needed to show its UI.
+ */
+@Composable
+private fun ContentScope.CardForeground(
+ viewModel: MediaCardViewModel,
+ threeRows: Boolean,
+ fillHeight: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ // Can't use a Crossfade composable because of the custom layout logic below. Animate the alpha
+ // of the guts (and, indirectly, of the content) from here.
+ val gutsAlphaAnimatable = remember { Animatable(0f) }
+ val isGutsVisible = viewModel.guts.isVisible
+ LaunchedEffect(isGutsVisible) { gutsAlphaAnimatable.animateTo(if (isGutsVisible) 1f else 0f) }
+
+ val colorScheme = rememberAnimatedColorScheme(viewModel.colorScheme)
+
+ // Use a custom layout to measure the content even if the content is being hidden because the
+ // internal guts are showing. This is needed because only the content knows the size the of the
+ // card and the guts are set to be the same size of the content.
+ Layout(
+ content = {
+ CardForegroundContent(
+ viewModel = viewModel,
+ threeRows = threeRows,
+ fillHeight = fillHeight,
+ colorScheme = colorScheme,
+ modifier =
+ Modifier.graphicsLayer {
+ compositingStrategy = CompositingStrategy.ModulateAlpha
+ alpha = 1f - gutsAlphaAnimatable.value
+ },
+ )
+
+ CardGuts(
+ viewModel = viewModel.guts,
+ colorScheme = colorScheme,
+ modifier =
+ Modifier.graphicsLayer {
+ compositingStrategy = CompositingStrategy.ModulateAlpha
+ alpha = gutsAlphaAnimatable.value
+ },
+ )
+ },
+ modifier = modifier,
+ ) { measurables, constraints ->
+ check(measurables.size == 2)
+ val contentPlaceable = measurables[0].measure(constraints)
+ // Guts should always have the exact dimensions as the content, even if we don't show the
+ // content.
+ val gutsPlaceable =
+ measurables[1].measure(
+ Constraints.fixed(contentPlaceable.width, contentPlaceable.height)
+ )
+
+ layout(contentPlaceable.measuredWidth, contentPlaceable.measuredHeight) {
+ if (!viewModel.guts.isVisible || gutsAlphaAnimatable.isRunning) {
+ contentPlaceable.place(0, 0)
+ }
+ if (viewModel.guts.isVisible || gutsAlphaAnimatable.isRunning) {
+ gutsPlaceable.place(0, 0)
+ }
+ }
+ }
+}
+
+@Composable
+private fun ContentScope.CardForegroundContent(
+ viewModel: MediaCardViewModel,
+ threeRows: Boolean,
+ fillHeight: Boolean,
+ colorScheme: AnimatedColorScheme,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier =
+ modifier.combinedClickable(
+ onClick = viewModel.onClick,
+ onLongClick = viewModel.onLongClick,
+ onClickLabel = viewModel.onClickLabel,
+ )
+ ) {
+ // Always add the first/top row, regardless of presentation style.
+ Box(modifier = Modifier.fillMaxWidth()) {
+ // Icon.
+ Icon(
+ icon = viewModel.icon,
+ tint = colorScheme.primary,
+ modifier =
+ Modifier.align(Alignment.TopStart)
+ .padding(top = 16.dp, start = 16.dp)
+ .size(24.dp)
+ .clip(CircleShape),
+ )
+
+ var cardMaxWidth: Int by remember { mutableIntStateOf(0) }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier =
+ Modifier.align(Alignment.TopEnd)
+ // Output switcher chips must each be limited to at most 40% of the maximum
+ // width of the card.
+ //
+ // This saves the maximum possible width of the card so it can be referred
+ // to by child custom layout code below.
+ //
+ // The assumption is that the row can be as wide as the entire card.
+ .layout { measurable, constraints ->
+ cardMaxWidth = constraints.maxWidth
+ val placeable = measurable.measure(constraints)
+
+ layout(placeable.measuredWidth, placeable.measuredHeight) {
+ placeable.place(0, 0)
+ }
+ },
+ ) {
+ viewModel.outputSwitcherChips.fastForEach { chip ->
+ OutputSwitcherChip(
+ viewModel = chip,
+ colorScheme = colorScheme,
+ modifier =
+ Modifier
+ // Each chip must be limited to 40% of the width of the card at
+ // most.
+ //
+ // The underlying assumption is that there'll never be more than one
+ // chip with text and one more icon-only chip. Only the one with
+ // text can ever end up being too wide.
+ .layout { measurable, constraints ->
+ val placeable =
+ measurable.measure(
+ constraints.copy(
+ maxWidth =
+ min(
+ (cardMaxWidth * 0.4f).fastRoundToInt(),
+ constraints.maxWidth,
+ )
+ )
+ )
+
+ layout(placeable.measuredWidth, placeable.measuredHeight) {
+ placeable.place(0, 0)
+ }
+ },
+ )
+ }
+ }
+ }
+
+ // If the card is taller than necessary to show all the rows, this adds spacing
+ // between the top row and the rows below, anchoring the next rows to the bottom
+ // of the card.
+ if (fillHeight) {
+ Spacer(Modifier.weight(1f))
+ }
+
+ if (threeRows) {
+ // Three row presentation style.
+ //
+ // Second row.
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp),
+ ) {
+ Metadata(
+ title = viewModel.title,
+ subtitle = viewModel.subtitle,
+ color = Color.White,
+ modifier = Modifier.weight(1f).padding(end = 8.dp),
+ )
+
+ if (viewModel.actionButtonLayout == MediaCardActionButtonLayout.WithPlayPause) {
+ AnimatedVisibility(visible = viewModel.playPauseAction != null) {
+ PlayPauseAction(
+ viewModel = checkNotNull(viewModel.playPauseAction),
+ buttonWidth = 48.dp,
+ buttonColor = colorScheme.primary,
+ iconColor = colorScheme.onPrimary,
+ buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 48.dp },
+ )
+ }
+ }
+ }
+
+ // Third row.
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp, bottom = 16.dp),
+ ) {
+ Navigation(
+ viewModel = viewModel.navigation,
+ isSeekBarVisible = true,
+ areActionsVisible =
+ viewModel.actionButtonLayout == MediaCardActionButtonLayout.WithPlayPause,
+ modifier = Modifier.weight(1f),
+ )
+
+ viewModel.additionalActions.fastForEachIndexed { index, action ->
+ SecondaryAction(
+ viewModel = action,
+ element = Media.Elements.additionalActionButton(index),
+ )
+ }
+ }
+ } else {
+ // Two row presentation style.
+ //
+ // Bottom row.
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(start = 16.dp, top = 36.dp, end = 16.dp, bottom = 16.dp),
+ ) {
+ Metadata(
+ title = viewModel.title,
+ subtitle = viewModel.subtitle,
+ color = Color.White,
+ modifier = Modifier.weight(1f).padding(end = 8.dp),
+ )
+
+ Navigation(
+ viewModel = viewModel.navigation,
+ isSeekBarVisible = false,
+ areActionsVisible =
+ viewModel.actionButtonLayout == MediaCardActionButtonLayout.WithPlayPause,
+ modifier = Modifier.padding(end = 8.dp),
+ )
+
+ if (
+ viewModel.actionButtonLayout == MediaCardActionButtonLayout.SecondaryActionsOnly
+ ) {
+ viewModel.additionalActions.fastForEachIndexed { index, action ->
+ SecondaryAction(
+ viewModel = action,
+ element = Media.Elements.additionalActionButton(index),
+ modifier = Modifier.padding(end = 8.dp),
+ )
+ }
+ }
+
+ AnimatedVisibility(visible = viewModel.playPauseAction != null) {
+ PlayPauseAction(
+ viewModel = checkNotNull(viewModel.playPauseAction),
+ buttonWidth = 48.dp,
+ buttonColor = colorScheme.primary,
+ iconColor = colorScheme.onPrimary,
+ buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 48.dp },
+ )
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Renders a simplified version of [CardForeground] that puts everything on a single row and doesn't
+ * support the guts.
+ */
+@Composable
+private fun ContentScope.CompactCardForeground(
+ viewModel: MediaCardViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ modifier
+ .clickable(onClick = viewModel.onClick, onClickLabel = viewModel.onClickLabel)
+ .background(MaterialTheme.colorScheme.surfaceContainer)
+ .padding(16.dp),
+ ) {
+ Icon(
+ icon = viewModel.icon,
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.size(24.dp),
+ )
+
+ Metadata(
+ title = viewModel.title,
+ subtitle = viewModel.subtitle,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.weight(1f),
+ )
+
+ SecondaryAction(
+ viewModel = viewModel.outputSwitcherChipButton,
+ element = Media.Elements.OutputSwitcherButton,
+ iconColor = MaterialTheme.colorScheme.onSurface,
+ )
+
+ val rightAction = (viewModel.navigation as? MediaNavigationViewModel.Showing)?.right
+ if (rightAction != null) {
+ SecondaryAction(
+ viewModel = rightAction,
+ element = Media.Elements.NextButton,
+ iconColor = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+
+ AnimatedVisibility(visible = viewModel.playPauseAction != null) {
+ PlayPauseAction(
+ viewModel = checkNotNull(viewModel.playPauseAction),
+ buttonWidth = 72.dp,
+ buttonColor = MaterialTheme.colorScheme.primaryContainer,
+ iconColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 24.dp },
+ )
+ }
+ }
+}
+
+/** Renders the background of a card, loading the artwork and showing an overlay on top of it. */
+@Composable
+private fun CardBackground(image: ImageBitmap?, modifier: Modifier = Modifier) {
+ Crossfade(targetState = image, modifier = modifier) { imageOrNull ->
+ if (imageOrNull != null) {
+ // Loaded art.
+ val gradientBaseColor = MaterialTheme.colorScheme.onSurface
+ Image(
+ bitmap = imageOrNull,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier =
+ Modifier.fillMaxSize().drawWithContent {
+ // Draw the content (loaded art).
+ drawContent()
+
+ if (image != null) {
+ // Then draw the overlay.
+ drawRect(
+ brush =
+ Brush.radialGradient(
+ 0f to gradientBaseColor.copy(alpha = 0.65f),
+ 1f to gradientBaseColor.copy(alpha = 0.75f),
+ center = size.center,
+ radius = max(size.width, size.height) / 2,
+ )
+ )
+ }
+ },
+ )
+ } else {
+ // Placeholder.
+ Box(Modifier.background(MaterialTheme.colorScheme.onSurface).fillMaxSize())
+ }
+ }
+}
+
+/**
+ * Renders the navigation UI (seek bar and/or previous/next buttons).
+ *
+ * If [isSeekBarVisible] is `false`, the seek bar will not be included in the layout, even if it
+ * would otherwise be showing based on the view-model alone. This is meant for callers to decide
+ * whether they'd like to show the seek bar in addition to the prev/next buttons or just show the
+ * buttons.
+ *
+ * If [areActionsVisible] is `false`, the left/right buttons to the left and right of the seek bar
+ * will not be included in the layout.
+ */
+@Composable
+private fun ContentScope.Navigation(
+ viewModel: MediaNavigationViewModel,
+ isSeekBarVisible: Boolean,
+ areActionsVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ when (viewModel) {
+ is MediaNavigationViewModel.Showing -> {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier,
+ ) {
+ if (areActionsVisible) {
+ SecondaryAction(viewModel = viewModel.left, element = Media.Elements.PrevButton)
+ }
+
+ val interactionSource = remember { MutableInteractionSource() }
+ val colors =
+ colors(
+ activeTrackColor = Color.White,
+ inactiveTrackColor = Color.White.copy(alpha = 0.3f),
+ thumbColor = Color.White,
+ )
+ if (isSeekBarVisible) {
+ // To allow the seek bar slider to fade in and out, it's tagged as an element.
+ Element(key = Media.Elements.SeekBarSlider, modifier = Modifier.weight(1f)) {
+ val sliderDragDelta = remember {
+ // Not a mutableStateOf - this is never accessed in composition and
+ // using an anonymous object avoids generics boxing of inline Offset.
+ object {
+ var value = Offset.Zero
+ }
+ }
+ Slider(
+ interactionSource = interactionSource,
+ value = viewModel.progress,
+ onValueChange = { progress -> viewModel.onScrubChange(progress) },
+ onValueChangeFinished = {
+ viewModel.onScrubFinished(sliderDragDelta.value)
+ },
+ colors = colors,
+ thumb = {
+ SeekBarThumb(interactionSource = interactionSource, colors = colors)
+ },
+ track = { sliderState ->
+ SeekBarTrack(
+ sliderState = sliderState,
+ isSquiggly = viewModel.isSquiggly,
+ colors = colors,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ },
+ modifier =
+ Modifier.fillMaxWidth()
+ .clearAndSetSemantics {
+ contentDescription = viewModel.contentDescription
+ }
+ .pointerInput(Unit) {
+ // Track and report the drag delta to the view-model so it
+ // can
+ // decide whether to accept the next onValueChangeFinished
+ // or
+ // reject it if the drag was overly vertical.
+ awaitPointerEventScope {
+ var down: PointerInputChange? = null
+ while (true) {
+ val event =
+ awaitPointerEvent(PointerEventPass.Initial)
+ when (event.type) {
+ PointerEventType.Press -> {
+ // A new gesture has begun. Record the
+ // initial
+ // down input change.
+ down = event.changes.last()
+ }
+
+ PointerEventType.Move -> {
+ // The pointer has moved. If it's the same
+ // pointer as the latest down, calculate and
+ // report the drag delta.
+ val change = event.changes.last()
+ if (change.id == down?.id) {
+ sliderDragDelta.value =
+ change.position - down.position
+ }
+ }
+ }
+ }
+ }
+ },
+ )
+ }
+ }
+
+ if (areActionsVisible) {
+ SecondaryAction(
+ viewModel = viewModel.right,
+ element = Media.Elements.NextButton,
+ )
+ }
+ }
+ }
+
+ is MediaNavigationViewModel.Hidden -> Unit
+ }
+}
+
+/** Renders the thumb of the seek bar. */
+@Composable
+private fun SeekBarThumb(
+ interactionSource: MutableInteractionSource,
+ colors: SliderColors,
+ modifier: Modifier = Modifier,
+) {
+ val interactions = remember { mutableStateListOf<Interaction>() }
+ LaunchedEffect(interactionSource) {
+ interactionSource.interactions.collect { interaction ->
+ when (interaction) {
+ is PressInteraction.Press -> interactions.add(interaction)
+ is PressInteraction.Release -> interactions.remove(interaction.press)
+ is PressInteraction.Cancel -> interactions.remove(interaction.press)
+ is DragInteraction.Start -> interactions.add(interaction)
+ is DragInteraction.Stop -> interactions.remove(interaction.start)
+ is DragInteraction.Cancel -> interactions.remove(interaction.start)
+ }
+ }
+ }
+
+ Spacer(
+ modifier
+ .size(width = 4.dp, height = 16.dp)
+ .hoverable(interactionSource = interactionSource)
+ .background(color = colors.thumbColor, shape = RoundedCornerShape(16.dp))
+ )
+}
+
+/**
+ * Renders the track of the seek bar.
+ *
+ * If [isSquiggly] is `true`, the part to the left of the thumb will animate a squiggly line that
+ * oscillates up and down. The [waveLength] and [amplitude] control the geometry of the squiggle and
+ * the [waveSpeedDpPerSec] controls the speed by which it seems to "move" horizontally.
+ */
+@Composable
+private fun SeekBarTrack(
+ sliderState: SliderState,
+ isSquiggly: Boolean,
+ colors: SliderColors,
+ modifier: Modifier = Modifier,
+ waveLength: Dp = 20.dp,
+ amplitude: Dp = 3.dp,
+ waveSpeedDpPerSec: Dp = 8.dp,
+) {
+ // Animating the amplitude allows the squiggle to gradually grow to its full height or shrink
+ // back to a flat line as needed.
+ val animatedAmplitude by
+ animateDpAsState(
+ targetValue = if (isSquiggly) amplitude else 0.dp,
+ label = "SeekBarTrack.amplitude",
+ )
+
+ // This animates the horizontal movement of the squiggle.
+ val animatedWaveOffset = remember { Animatable(0f) }
+
+ LaunchedEffect(isSquiggly) {
+ if (isSquiggly) {
+ animatedWaveOffset.snapTo(0f)
+ animatedWaveOffset.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ infiniteRepeatable(
+ animation =
+ tween(
+ durationMillis = (1000 * (waveLength / waveSpeedDpPerSec)).toInt(),
+ easing = LinearEasing,
+ ),
+ repeatMode = RepeatMode.Restart,
+ ),
+ )
+ }
+ }
+
+ // Render the track.
+ Canvas(modifier = modifier) {
+ val thumbPositionPx = size.width * sliderState.value
+
+ // The squiggly part before the thumb.
+ if (thumbPositionPx > 0) {
+ val amplitudePx = amplitude.toPx()
+ val animatedAmplitudePx = animatedAmplitude.toPx()
+ val waveLengthPx = waveLength.toPx()
+
+ val path =
+ Path().apply {
+ val halfWaveLengthPx = waveLengthPx / 2
+ val halfWaveCount = (thumbPositionPx / halfWaveLengthPx).toInt()
+
+ repeat(halfWaveCount + 3) { index ->
+ // Draw a half wave (either a hill or a valley shape starting and ending on
+ // the horizontal center).
+ relativeQuadraticTo(
+ // The control point for the bezier curve is on top of the peak of the
+ // hill or the very center bottom of the valley shape.
+ dx1 = halfWaveLengthPx / 2,
+ dy1 = if (index % 2 == 0) -animatedAmplitudePx else animatedAmplitudePx,
+ // Advance horizontally, half a wave length at a time.
+ dx2 = halfWaveLengthPx,
+ dy2 = 0f,
+ )
+ }
+ }
+
+ // Now that the squiggle is rendered a bit past the thumb, clip off the part that passed
+ // the thumb. It's easier to clip the extra squiggle than to figure out the bezier curve
+ // for part of a hill/valley.
+ clipRect(
+ left = 0f,
+ top = -amplitudePx,
+ right = thumbPositionPx,
+ bottom = amplitudePx * 2,
+ ) {
+ translate(left = -waveLengthPx * animatedWaveOffset.value, top = 0f) {
+ // Actually render the squiggle.
+ drawPath(
+ path = path,
+ color = colors.activeTrackColor,
+ style = Stroke(width = 2.dp.toPx(), cap = StrokeCap.Round),
+ )
+ }
+ }
+ }
+
+ // The flat line after the thumb.
+ drawLine(
+ color = colors.inactiveTrackColor,
+ start = Offset(thumbPositionPx, 0f),
+ end = Offset(size.width, 0f),
+ strokeWidth = 2.dp.toPx(),
+ cap = StrokeCap.Round,
+ )
+ }
+}
+
+/** Renders the internal "guts" of a card. */
+@Composable
+private fun CardGuts(
+ viewModel: MediaCardGutsViewModel,
+ colorScheme: AnimatedColorScheme,
+ modifier: Modifier = Modifier,
+) {
+ Box(
+ modifier =
+ modifier.pointerInput(Unit) { detectLongPressGesture { viewModel.onLongClick() } }
+ ) {
+ // Settings button.
+ Icon(
+ icon = checkNotNull(viewModel.settingsButton.icon),
+ modifier =
+ Modifier.align(Alignment.TopEnd).padding(top = 16.dp, end = 16.dp).clickable {
+ viewModel.settingsButton.onClick()
+ },
+ )
+
+ // Content.
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier =
+ Modifier.align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 32.dp, bottom = 40.dp),
+ ) {
+ Text(text = viewModel.text, color = Color.White)
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ PlatformButton(
+ onClick = viewModel.primaryAction.onClick,
+ colors = ButtonDefaults.buttonColors(containerColor = Color.White),
+ ) {
+ Text(
+ text = checkNotNull(viewModel.primaryAction.text),
+ color = colorScheme.onPrimary,
+ )
+ }
+
+ viewModel.secondaryAction?.let { button ->
+ PlatformOutlinedButton(
+ onClick = button.onClick,
+ border = BorderStroke(width = 1.dp, color = Color.White),
+ ) {
+ Text(text = checkNotNull(button.text), color = Color.White)
+ }
+ }
+ }
+ }
+ }
+}
+
+/** Renders the metadata labels of a track. */
+@Composable
+private fun ContentScope.Metadata(
+ title: String,
+ subtitle: String,
+ color: Color,
+ modifier: Modifier = Modifier,
+) {
+ // This element can be animated when switching between scenes inside a media card.
+ Element(key = Media.Elements.Metadata, modifier = modifier) {
+ // When the title and/or subtitle change, crossfade between the old and the new.
+ Crossfade(targetState = title to subtitle, label = "Labels.crossfade") { (title, subtitle)
+ ->
+ Column {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.bodyLarge,
+ color = color,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+
+ Text(
+ text = subtitle,
+ style = MaterialTheme.typography.bodyMedium,
+ color = color,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+ }
+}
+
+/**
+ * Renders a small chip showing the current output device and providing a way to switch to a
+ * different output device.
+ */
+@Composable
+private fun OutputSwitcherChip(
+ viewModel: MediaOutputSwitcherChipViewModel,
+ colorScheme: AnimatedColorScheme,
+ modifier: Modifier = Modifier,
+) {
+ // For accessibility reasons, the touch area for the chip needs to be at least 48dp in height.
+ // At the same time, the rounded corner chip should only be as tall as it needs to be to contain
+ // its contents and look like a nice design; also, the ripple effect should only be shown within
+ // the bounds of the chip.
+ //
+ // This is achieved by sharing this InteractionSource between the outer and inner composables.
+ //
+ // The outer composable hosts that clickable that writes user events into the InteractionSource.
+ // The inner composable consumes the user events from the InteractionSource and feeds them into
+ // its indication.
+ val clickInteractionSource = remember { MutableInteractionSource() }
+ Box(
+ modifier =
+ modifier
+ .height(48.dp)
+ .clickable(interactionSource = clickInteractionSource, indication = null) {
+ viewModel.onClick()
+ }
+ .padding(top = 16.dp, end = 16.dp, bottom = 8.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.clip(RoundedCornerShape(12.dp))
+ .background(colorScheme.primary)
+ .indication(clickInteractionSource, ripple())
+ .padding(start = 8.dp, end = 12.dp, top = 4.dp, bottom = 4.dp),
+ ) {
+ Icon(
+ icon = viewModel.icon,
+ tint = colorScheme.onPrimary,
+ modifier = Modifier.size(16.dp),
+ )
+
+ viewModel.text?.let {
+ Text(
+ text = viewModel.text,
+ style = MaterialTheme.typography.bodySmall,
+ color = colorScheme.onPrimary,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+ }
+}
+
+/** Renders the primary action of media controls: the play/pause button. */
+@Composable
+private fun ContentScope.PlayPauseAction(
+ viewModel: MediaPlayPauseActionViewModel,
+ buttonWidth: Dp,
+ buttonColor: Color,
+ iconColor: Color,
+ buttonCornerRadius: (isPlaying: Boolean) -> Dp,
+ modifier: Modifier = Modifier,
+) {
+ val cornerRadius: Dp by
+ animateDpAsState(
+ targetValue = buttonCornerRadius(viewModel.state != MediaSessionState.Paused),
+ label = "PlayPauseAction.cornerRadius",
+ )
+ // This element can be animated when switching between scenes inside a media card.
+ Element(key = Media.Elements.PlayPauseButton, modifier = modifier) {
+ PlatformButton(
+ onClick = viewModel.onClick ?: {},
+ enabled = viewModel.onClick != null,
+ colors = ButtonDefaults.buttonColors(containerColor = buttonColor),
+ shape = RoundedCornerShape(cornerRadius),
+ modifier = Modifier.size(width = buttonWidth, height = 48.dp),
+ ) {
+ when (viewModel.state) {
+ is MediaSessionState.Playing,
+ is MediaSessionState.Paused -> {
+ val painterOrNull =
+ when (viewModel.icon) {
+ // TODO(b/399860531): load this expensive-to-load animated vector
+ // drawable off the main thread.
+ is Icon.Resource ->
+ rememberAnimatedVectorPainter(
+ animatedImageVector =
+ AnimatedImageVector.animatedVectorResource(
+ id = viewModel.icon.res
+ ),
+ atEnd = viewModel.state == MediaSessionState.Playing,
+ )
+ is Icon.Loaded -> rememberDrawablePainter(viewModel.icon.drawable)
+ null -> null
+ }
+ painterOrNull?.let { painter ->
+ Icon(
+ painter = painter,
+ contentDescription = viewModel.icon?.contentDescription?.load(),
+ tint = iconColor,
+ modifier = Modifier.size(24.dp),
+ )
+ }
+ }
+ is MediaSessionState.Buffering -> {
+ CircularProgressIndicator(color = iconColor, modifier = Modifier.size(24.dp))
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Renders an icon button for an action that's not the play/pause action.
+ *
+ * If [element] is provided, the secondary action element will be able to animate when switching
+ * between scenes inside a media card.
+ */
+@Composable
+private fun ContentScope.SecondaryAction(
+ viewModel: MediaSecondaryActionViewModel,
+ modifier: Modifier = Modifier,
+ element: ElementKey? = null,
+ iconColor: Color = Color.White,
+) {
+ if (viewModel !is MediaSecondaryActionViewModel.None && element != null) {
+ Element(key = element, modifier = modifier) {
+ SecondaryActionContent(viewModel = viewModel, iconColor = iconColor)
+ }
+ } else {
+ SecondaryActionContent(viewModel = viewModel, iconColor = iconColor, modifier = modifier)
+ }
+}
+
+/** The content of a [SecondaryAction]. */
+@Composable
+private fun SecondaryActionContent(
+ viewModel: MediaSecondaryActionViewModel,
+ iconColor: Color,
+ modifier: Modifier = Modifier,
+) {
+ val sharedModifier = modifier.size(48.dp).padding(13.dp)
+ when (viewModel) {
+ is MediaSecondaryActionViewModel.Action ->
+ PlatformIconButton(
+ onClick = viewModel.onClick ?: {},
+ iconResource = (viewModel.icon as Icon.Resource).res,
+ contentDescription = viewModel.icon.contentDescription?.load(),
+ colors = IconButtonDefaults.iconButtonColors(contentColor = iconColor),
+ enabled = viewModel.onClick != null,
+ modifier = sharedModifier,
+ )
+
+ is MediaSecondaryActionViewModel.ReserveSpace -> Spacer(modifier = sharedModifier)
+
+ is MediaSecondaryActionViewModel.None -> Unit
+ }
+}
+
+/** Enumerates all supported media presentation styles. */
+enum class MediaPresentationStyle {
+ /** The "normal" 3-row carousel look. */
+ Default,
+ /** Similar to [Default] but not as tall (2-row carousel look). */
+ Compressed,
+ /** A special single-row treatment that fits nicely in quick settings. */
+ Compact,
+}
+
+data class MediaUiBehavior(
+ val isCarouselDismissible: Boolean = true,
+ val isCarouselScrollingEnabled: Boolean = true,
+ val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty,
+ /**
+ * If provided, this callback will be consulted at the beginning of each carousel scroll gesture
+ * to see if the falsing system thinks that it's a false touch. If it then returns `true`, the
+ * scroll will be canceled.
+ */
+ val isCarouselScrollFalseTouch: (() -> Boolean)? = null,
+)
+
+@Stable
+private interface AnimatedColorScheme {
+ val primary: Color
+ val onPrimary: Color
+}
+
+private object Media {
+
+ /**
+ * Scenes.
+ *
+ * The implementation uses a [SceneTransitionLayout] to smoothly animate transitions between
+ * different card layouts. Each card layout is identified as its own "scene" and the STL
+ * framework takes care of animating the layouts and their elements as the card morphs between
+ * scenes.
+ */
+ object Scenes {
+ /** The "normal" 3-row carousel look. */
+ val Default = SceneKey("default")
+ /** Similar to [Default] but not as tall (2-row carousel look). */
+ val Compressed = SceneKey("compressed")
+ /** A special single-row treatment that fits nicely in quick settings. */
+ val Compact = SceneKey("compact")
+ }
+
+ /** Definitions of how scene changes are transition-animated. */
+ val Transitions = transitions {
+ from(Scenes.Default, to = Scenes.Compact) {}
+ from(Scenes.Default, to = Scenes.Compressed) { fade(Elements.SeekBarSlider) }
+ from(Scenes.Compact, to = Scenes.Compressed) { fade(Elements.SeekBarSlider) }
+ }
+
+ /**
+ * Element keys.
+ *
+ * Composables that are wrapped in [ContentScope.Element] with one of these as their `key`
+ * parameter will automatically be picked up by the STL transition animation framework and will
+ * be animated from their bounds in the original scene to their bounds in the destination scene.
+ *
+ * In addition, tagging such elements with a key allows developers to customize the transition
+ * animations even further.
+ */
+ object Elements {
+ val PlayPauseButton = ElementKey("play_pause")
+ val Metadata = ElementKey("metadata")
+ val PrevButton = ElementKey("prev")
+ val NextButton = ElementKey("next")
+ val SeekBarSlider = ElementKey("seek_bar_slider")
+ val OutputSwitcherButton = ElementKey("output_switcher")
+
+ fun additionalActionButton(index: Int): ElementKey {
+ return ElementKey("additional_action_$index")
+ }
+ }
+}
+
+private fun MediaPresentationStyle.toScene(): SceneKey {
+ return when (this) {
+ MediaPresentationStyle.Default -> Media.Scenes.Default
+ MediaPresentationStyle.Compressed -> Media.Scenes.Compressed
+ MediaPresentationStyle.Compact -> Media.Scenes.Compact
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt
new file mode 100644
index 000000000000..61e3bdced5f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+data class MediaCardGutsViewModel(
+ val isVisible: Boolean,
+ val text: String,
+ val primaryAction: MediaGutsButtonViewModel,
+ val secondaryAction: MediaGutsButtonViewModel? = null,
+ val settingsButton: MediaGutsSettingsButtonViewModel,
+ val onLongClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt
new file mode 100644
index 000000000000..833a04ddcb55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.graphics.ImageBitmap
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout
+import com.android.systemui.media.remedia.shared.model.MediaColorScheme
+
+/** Models UI state for a media card. */
+@Stable
+interface MediaCardViewModel {
+ /**
+ * Identifier. Must be unique across all media cards currently shown, to help the horizontal
+ * pager in the UI.
+ */
+ val key: Any
+
+ val icon: Icon
+
+ val background: ImageBitmap?
+
+ val colorScheme: MediaColorScheme
+
+ val title: String
+
+ val subtitle: String
+
+ val actionButtonLayout: MediaCardActionButtonLayout
+
+ val playPauseAction: MediaPlayPauseActionViewModel?
+
+ val navigation: MediaNavigationViewModel
+
+ val additionalActions: List<MediaSecondaryActionViewModel>
+
+ val guts: MediaCardGutsViewModel
+
+ val outputSwitcherChips: List<MediaOutputSwitcherChipViewModel>
+
+ /** Simple icon-only version of the output switcher for use in compact UIs. */
+ val outputSwitcherChipButton: MediaSecondaryActionViewModel.Action
+
+ val onClick: () -> Unit
+
+ /** Accessibility string for the click action of the card. */
+ val onClickLabel: String?
+
+ val onLongClick: () -> Unit
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt
new file mode 100644
index 000000000000..53aa87ce0843
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+/** Enumerates the known rules for media carousel visibility. */
+enum class MediaCarouselVisibility {
+
+ /** The carousel should be shown as long as it has at least one card. */
+ WhenNotEmpty,
+
+ /**
+ * The carousel should be shown as long as it has at least one card that represents an active
+ * media session. In other words: if all cards in the carousel represent _inactive_ sessions,
+ * the carousel should _not_ be visible.
+ */
+ WhenAnyCardIsActive,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt
new file mode 100644
index 000000000000..6ce0a014455f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+data class MediaGutsButtonViewModel(val text: String, val onClick: () -> Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt
new file mode 100644
index 000000000000..fabfe0e147c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+data class MediaGutsSettingsButtonViewModel(val icon: Icon, val onClick: () -> Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt
new file mode 100644
index 000000000000..a3689926e937
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import androidx.annotation.FloatRange
+import androidx.compose.ui.geometry.Offset
+
+/**
+ * Models UI state for the navigation component of the UI (potentially containing the seek bar and
+ * the buttons to its left and right).
+ */
+sealed interface MediaNavigationViewModel {
+
+ /** The seek bar should be showing. */
+ data class Showing(
+ /** The progress to show on the seek bar, between `0` and `1`. */
+ @FloatRange(from = 0.0, to = 1.0) val progress: Float,
+ /**
+ * The action button to the left of the seek bar; or `null` if it should be absent in the
+ * UI.
+ */
+ val left: MediaSecondaryActionViewModel,
+ /**
+ * The action button to the right of the seek bar; or `null` if it should be absent in the
+ * UI.
+ */
+ val right: MediaSecondaryActionViewModel,
+ /**
+ * Whether the portion of the seek bar track before the thumb should show the squiggle
+ * animation.
+ */
+ val isSquiggly: Boolean,
+ /**
+ * Whether the UI should show as "scrubbing" because the user is actively moving the thumb
+ * of the seek bar.
+ */
+ val isScrubbing: Boolean,
+ /**
+ * A callback to invoke while the user is "scrubbing" (e.g. actively moving the thumb of the
+ * seek bar). The position/progress of the actual track should not be changed during this
+ * time.
+ */
+ val onScrubChange: (progress: Float) -> Unit,
+ /**
+ * A callback to invoke once the user finishes "scrubbing" (e.g. stopped moving the thumb of
+ * the seek bar). The position/progress should be committed.
+ */
+ val onScrubFinished: (delta: Offset) -> Unit,
+ /** Accessibility string to attach to the seekbar UI element. */
+ val contentDescription: String,
+ ) : MediaNavigationViewModel
+
+ /** The seek bar should be hidden. */
+ data object Hidden : MediaNavigationViewModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt
new file mode 100644
index 000000000000..f2724da2c3af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+data class MediaOutputSwitcherChipViewModel(
+ val icon: Icon,
+ val text: String? = null,
+ val onClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt
new file mode 100644
index 000000000000..ecc92d778f8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.media.remedia.shared.model.MediaSessionState
+
+/** Models UI state for the play/pause action button within media controls. */
+data class MediaPlayPauseActionViewModel(
+ val state: MediaSessionState,
+ val icon: Icon?,
+ val onClick: (() -> Unit)?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt
new file mode 100644
index 000000000000..d28ca7ab7121
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+/** Models UI state for a secondary action button within media controls. */
+sealed interface MediaSecondaryActionViewModel {
+ data class Action(val icon: Icon, val onClick: (() -> Unit)?) : MediaSecondaryActionViewModel
+
+ data object ReserveSpace : MediaSecondaryActionViewModel
+
+ data object None : MediaSecondaryActionViewModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt
new file mode 100644
index 000000000000..19b08fa212db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.remedia.ui.viewmodel
+
+import android.content.Context
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.ImageBitmap
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.media.remedia.domain.interactor.MediaInteractor
+import com.android.systemui.media.remedia.domain.model.MediaActionModel
+import com.android.systemui.media.remedia.shared.model.MediaColorScheme
+import com.android.systemui.media.remedia.shared.model.MediaSessionState
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Locale
+import kotlin.math.abs
+import kotlin.math.roundToLong
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.awaitCancellation
+
+/** Models UI state for a media element. */
+class MediaViewModel
+@AssistedInject
+constructor(
+ private val interactor: MediaInteractor,
+ private val falsingSystem: FalsingSystem,
+ @Assisted private val context: Context,
+ @Assisted private val carouselVisibility: MediaCarouselVisibility,
+) : ExclusiveActivatable() {
+
+ /** Whether the user is actively moving the thumb of the seek bar. */
+ private var isScrubbing: Boolean by mutableStateOf(false)
+ /** The position of the thumb of the seek bar as the user is scrubbing it. */
+ private var seekProgress: Float by mutableFloatStateOf(0f)
+ /** Whether the internal "guts" are visible. */
+ private var isGutsVisible: Boolean by mutableStateOf(false)
+ /** The index of the currently-selected card. */
+ private var selectedCardIndex: Int by mutableIntStateOf(0)
+ private set
+
+ /** The current list of cards to show in the UI. */
+ val cards: List<MediaCardViewModel> by derivedStateOf {
+ interactor.sessions.mapIndexed { sessionIndex, session ->
+ val isCurrentSessionAndScrubbing = isScrubbing && sessionIndex == selectedCardIndex
+ object : MediaCardViewModel {
+ override val key = session.key
+ override val icon = session.appIcon
+ override val background: ImageBitmap?
+ get() = session.background
+
+ override val colorScheme: MediaColorScheme
+ get() = session.colorScheme
+
+ override val title = session.title
+ override val subtitle = session.subtitle
+ override val actionButtonLayout = session.actionButtonLayout
+ override val playPauseAction =
+ session.playPauseAction.toPlayPauseActionViewModel(session.state)
+ override val additionalActions: List<MediaSecondaryActionViewModel>
+ get() {
+ return session.additionalActions.map { action ->
+ action.toSecondaryActionViewModel()
+ }
+ }
+
+ override val navigation: MediaNavigationViewModel
+ get() {
+ return if (session.canBeScrubbed) {
+ MediaNavigationViewModel.Showing(
+ progress =
+ if (!isCurrentSessionAndScrubbing) {
+ session.positionMs.toFloat() / session.durationMs
+ } else {
+ seekProgress
+ },
+ left = session.leftAction.toSecondaryActionViewModel(),
+ right = session.rightAction.toSecondaryActionViewModel(),
+ isSquiggly =
+ session.state != MediaSessionState.Paused &&
+ !isCurrentSessionAndScrubbing,
+ isScrubbing = isCurrentSessionAndScrubbing,
+ onScrubChange = { progress ->
+ check(selectedCardIndex == sessionIndex) {
+ "Can't seek on a card that's not the selected card!"
+ }
+ isScrubbing = true
+ seekProgress = progress
+ },
+ onScrubFinished = { dragDelta ->
+ if (
+ dragDelta.isHorizontal() &&
+ !falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)
+ ) {
+ interactor.seek(
+ sessionKey = session.key,
+ to = (seekProgress * session.durationMs).roundToLong(),
+ )
+ }
+ isScrubbing = false
+ },
+ contentDescription =
+ context.getString(
+ R.string.controls_media_seekbar_description,
+ formatTimeContentDescription(session.positionMs),
+ formatTimeContentDescription(session.durationMs),
+ ),
+ )
+ } else {
+ MediaNavigationViewModel.Hidden
+ }
+ }
+
+ override val guts: MediaCardGutsViewModel
+ get() {
+ return MediaCardGutsViewModel(
+ isVisible = isGutsVisible,
+ text =
+ if (session.canBeHidden) {
+ context.getString(
+ R.string.controls_media_close_session,
+ session.appName,
+ )
+ } else {
+ context.getString(R.string.controls_media_active_session)
+ },
+ primaryAction =
+ if (session.canBeHidden) {
+ MediaGutsButtonViewModel(
+ text =
+ context.getString(
+ R.string.controls_media_dismiss_button
+ ),
+ onClick = {
+ falsingSystem.runIfNotFalseTap(
+ FalsingManager.LOW_PENALTY
+ ) {
+ interactor.hide(session.key)
+ isGutsVisible = false
+ }
+ },
+ )
+ } else {
+ MediaGutsButtonViewModel(
+ text = context.getString(R.string.cancel),
+ onClick = {
+ falsingSystem.runIfNotFalseTap(
+ FalsingManager.LOW_PENALTY
+ ) {
+ isGutsVisible = false
+ }
+ },
+ )
+ },
+ secondaryAction =
+ MediaGutsButtonViewModel(
+ text = context.getString(R.string.cancel),
+ onClick = {
+ falsingSystem.runIfNotFalseTap(
+ FalsingManager.LOW_PENALTY
+ ) {
+ isGutsVisible = false
+ }
+ },
+ )
+ .takeIf { session.canBeHidden },
+ settingsButton =
+ MediaGutsSettingsButtonViewModel(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_settings,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.controls_media_settings_button
+ ),
+ ),
+ onClick = {
+ falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) {
+ interactor.openMediaSettings()
+ }
+ },
+ ),
+ onLongClick = { isGutsVisible = false },
+ )
+ }
+
+ override val outputSwitcherChips: List<MediaOutputSwitcherChipViewModel>
+ get() {
+ return listOf(
+ MediaOutputSwitcherChipViewModel(
+ icon = session.outputDevice.icon,
+ text = session.outputDevice.name,
+ onClick = {
+ falsingSystem.runIfNotFalseTap(
+ FalsingManager.MODERATE_PENALTY
+ ) {
+ // TODO(b/397989775): tell the UI to show the output
+ // switcher.
+ }
+ },
+ )
+ )
+ }
+
+ override val outputSwitcherChipButton: MediaSecondaryActionViewModel.Action
+ get() {
+ return MediaSecondaryActionViewModel.Action(
+ icon = session.outputDevice.icon,
+ onClick = {
+ falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) {
+ // TODO(b/397989775): tell the UI to show the output switcher.
+ }
+ },
+ )
+ }
+
+ override val onClick = {
+ falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) { session.onClick() }
+ }
+ override val onClickLabel =
+ context.getString(R.string.controls_media_playing_item_description)
+ override val onLongClick = { isGutsVisible = true }
+ }
+ }
+ }
+
+ /** Whether the carousel should be visible. */
+ val isCarouselVisible: Boolean
+ get() =
+ when (carouselVisibility) {
+ MediaCarouselVisibility.WhenNotEmpty -> interactor.sessions.isNotEmpty()
+
+ MediaCarouselVisibility.WhenAnyCardIsActive ->
+ interactor.sessions.any { session -> session.isActive }
+ }
+
+ /** Notifies that the card at [cardIndex] has been selected in the UI. */
+ fun onCardSelected(cardIndex: Int) {
+ check(cardIndex >= 0 && cardIndex < cards.size)
+ selectedCardIndex = cardIndex
+ }
+
+ override suspend fun onActivated(): Nothing {
+ awaitCancellation()
+ }
+
+ private fun MediaActionModel.toPlayPauseActionViewModel(
+ mediaSessionState: MediaSessionState
+ ): MediaPlayPauseActionViewModel? {
+ return when (this) {
+ is MediaActionModel.Action ->
+ MediaPlayPauseActionViewModel(
+ state = mediaSessionState,
+ icon = icon,
+ onClick =
+ onClick?.let {
+ {
+ falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) {
+ it()
+ }
+ }
+ },
+ )
+ is MediaActionModel.None,
+ is MediaActionModel.ReserveSpace -> null
+ }
+ }
+
+ private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel {
+ return when (this) {
+ is MediaActionModel.Action ->
+ MediaSecondaryActionViewModel.Action(
+ icon = icon,
+ onClick =
+ onClick?.let {
+ {
+ falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) {
+ it()
+ }
+ }
+ },
+ )
+ is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace
+ is MediaActionModel.None -> MediaSecondaryActionViewModel.None
+ }
+ }
+
+ /**
+ * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds"
+ *
+ * Follows same logic as Chronometer#formatDuration
+ */
+ private fun formatTimeContentDescription(milliseconds: Long): String {
+ var seconds = milliseconds.milliseconds.inWholeSeconds
+
+ val hours =
+ if (seconds >= OneHourInSec) {
+ seconds / OneHourInSec
+ } else {
+ 0
+ }
+ seconds -= hours * OneHourInSec
+
+ val minutes =
+ if (seconds >= OneMinuteInSec) {
+ seconds / OneMinuteInSec
+ } else {
+ 0
+ }
+ seconds -= minutes * OneMinuteInSec
+
+ val measures = arrayListOf<Measure>()
+ if (hours > 0) {
+ measures.add(Measure(hours, MeasureUnit.HOUR))
+ }
+ if (minutes > 0) {
+ measures.add(Measure(minutes, MeasureUnit.MINUTE))
+ }
+ measures.add(Measure(seconds, MeasureUnit.SECOND))
+
+ return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(*measures.toTypedArray())
+ }
+
+ /**
+ * Returns `true` if this [Offset] is the same or larger on the horizontal axis than the
+ * vertical axis.
+ */
+ private fun Offset.isHorizontal(): Boolean {
+ return abs(x) >= abs(y)
+ }
+
+ interface FalsingSystem {
+ fun runIfNotFalseTap(@FalsingManager.Penalty penalty: Int, block: () -> Unit)
+
+ fun isFalseTouch(@Classifier.InteractionType interactionType: Int): Boolean
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel
+ }
+
+ companion object {
+ private const val OneMinuteInSec = 60
+ private const val OneHourInSec = OneMinuteInSec * 60
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
index 4ff54d4eae65..42d27619f60f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -26,7 +26,7 @@ import android.os.IBinder
import android.util.Log
import android.view.Display
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index ea515c96d3f0..4559a7aea1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -25,6 +25,8 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
@@ -35,6 +37,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* A plugin for [SysUiState] that provides overrides for certain state flags that must be pulled
@@ -46,17 +49,28 @@ class SceneContainerPlugin
constructor(
private val sceneInteractor: Lazy<SceneInteractor>,
private val occlusionInteractor: Lazy<SceneContainerOcclusionInteractor>,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
) {
+ private val shadeDisplayId: StateFlow<Int> by lazy { shadeDisplaysRepository.get().displayId }
+
/**
* Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
* or if the flag value doesn't need to be overridden.
*/
- fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? {
+ fun flagValueOverride(@SystemUiStateFlags flag: Long, displayId: Int): Boolean? {
if (!SceneContainerFlag.isEnabled) {
return null
}
+ if (ShadeWindowGoesAround.isEnabled && shadeDisplayId.value != displayId) {
+ // The shade is in another display. All flags related to the shade container will map to
+ // false on other displays now.
+ //
+ // Note that this assumes there is only one SceneContainer and it is only on the shade
+ // window display. If there will be more, this will need to be revisited
+ return false
+ }
val transitionState = sceneInteractor.get().transitionState.value
val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle
val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
new file mode 100644
index 000000000000..cec846e84f01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import com.android.systemui.shared.system.QuickStepContract.getSystemUiStateString
+
+/**
+ * Represents a set of state changes. A bit can either be set to `true` or `false`.
+ *
+ * This is used in [SysUIStateDisplaysInteractor] to selectively change bits.
+ */
+class StateChange {
+ private var flagsToSet: Long = 0
+ private var flagsToClear: Long = 0
+
+ /**
+ * Sets the [state] of the given [bit].
+ *
+ * @return `this` for chaining purposes
+ */
+ fun setFlag(bit: Long, state: Boolean): StateChange {
+ if (state) {
+ flagsToSet = flagsToSet or bit
+ flagsToClear = flagsToClear and bit.inv()
+ } else {
+ flagsToClear = flagsToClear or bit
+ flagsToSet = flagsToSet and bit.inv()
+ }
+ return this
+ }
+
+ /**
+ * Applies all changed flags to [sysUiState].
+ *
+ * Note this doesn't call [SysUiState.commitUpdate].
+ */
+ fun applyTo(sysUiState: SysUiState) {
+ iterateBits(flagsToSet or flagsToClear) { bit ->
+ val isBitSetInNewState = flagsToSet and bit != 0L
+ sysUiState.setFlag(bit, isBitSetInNewState)
+ }
+ }
+
+ fun applyTo(sysUiState: Long): Long {
+ var newState = sysUiState
+ newState = newState or flagsToSet
+ newState = newState and flagsToClear.inv()
+ return newState
+ }
+
+ private inline fun iterateBits(flags: Long, action: (bit: Long) -> Unit) {
+ var remaining = flags
+ while (remaining != 0L) {
+ val lowestBit = remaining and -remaining
+ action(lowestBit)
+
+ remaining -= lowestBit
+ }
+ }
+
+ /**
+ * Clears all the flags changed in a [sysUiState].
+ *
+ * Note this doesn't call [SysUiState.commitUpdate].
+ */
+ fun clearFrom(sysUiState: SysUiState) {
+ iterateBits(flagsToSet or flagsToClear) { bit -> sysUiState.setFlag(bit, false) }
+ }
+
+ /** Resets all the pending changes. */
+ fun clear() {
+ flagsToSet = 0
+ flagsToClear = 0
+ }
+
+ override fun toString(): String {
+ return """StateChange(flagsToSet=${getSystemUiStateString(flagsToSet)}, flagsToClear=${
+ getSystemUiStateString(
+ flagsToClear
+ )
+ })"""
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt
new file mode 100644
index 000000000000..2bdd9a88ad6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+/**
+ * Channels changes from several [SysUiState]s to a single callback.
+ *
+ * There are several [SysUiState]s (one per display). This class allows for listeners to listen to
+ * sysui state updates from any of those [SysUiState] instances.
+ *
+ * ┌────────────────────┐
+ * │ SysUIStateOverride │
+ * │ displayId=2 │
+ * └┬───────────────────┘
+ * │ ▲
+ * ┌───────────────┐ │ │ ┌────────────────────┐
+ * │ SysUIState │ │ │ │ SysUIStateOverride │
+ * │ displayId=0 │ │ │ │ displayId=1 │
+ * └────────────┬──┘ │ │ └┬───────────────────┘
+ * │ │ │ │ ▲
+ * ▼ ▼ │ ▼ │
+ * ┌─────────────┴─────┴─┐
+ * │SysUiStateDispatcher │
+ * └────────┬────────────┘
+ * │
+ * ▼
+ * ┌──────────────────┐
+ * │ listeners for │
+ * │ all displays │
+ * └──────────────────┘
+ */
+@SysUISingleton
+class SysUIStateDispatcher @Inject constructor() {
+
+ private val listeners = CopyOnWriteArrayList<SysUiState.SysUiStateCallback>()
+
+ /** Called from each [SysUiState] to propagate new state changes. */
+ fun dispatchSysUIStateChange(sysUiFlags: Long, displayId: Int) {
+ if (displayId != Display.DEFAULT_DISPLAY && !ShadeWindowGoesAround.isEnabled) return
+ listeners.forEach { listener ->
+ listener.onSystemUiStateChanged(sysUiFlags = sysUiFlags, displayId = displayId)
+ }
+ }
+
+ /**
+ * Registers a listener to listen for system UI state changes.
+ *
+ * Listeners will have [SysUiState.SysUiStateCallback.onSystemUiStateChanged] called whenever a
+ * system UI state changes.
+ */
+ fun registerListener(listener: SysUiState.SysUiStateCallback) {
+ listeners += listener
+ }
+
+ fun unregisterListener(listener: SysUiState.SysUiStateCallback) {
+ listeners -= listener
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt
new file mode 100644
index 000000000000..89e06e226997
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateOverride.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import android.view.Display
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * This class is used to provide per-display overrides for only certain flags.
+ *
+ * While some of the [SystemUiStateFlags] are per display (e.g. shade expansion, dialog visible),
+ * some of them are device specific (e.g. whether it's awake or not). A [SysUIStateOverride] is
+ * created for each display that is not [Display.DEFAULT_DISPLAY], and if some flags are set on it,
+ * they will override whatever the default display state had in those.
+ */
+class SysUIStateOverride
+@AssistedInject
+constructor(
+ @Assisted override val displayId: Int,
+ private val sceneContainerPlugin: SceneContainerPlugin?,
+ dumpManager: DumpManager,
+ private val defaultDisplayState: SysUiState,
+ private val stateDispatcher: SysUIStateDispatcher,
+) : SysUiStateImpl(displayId, sceneContainerPlugin, dumpManager, stateDispatcher) {
+
+ private val override = StateChange()
+ private var lastSentFlags = defaultDisplayState.flags
+
+ private val defaultFlagsChangedCallback = { _: Long, otherDisplayId: Int ->
+ if (otherDisplayId == Display.DEFAULT_DISPLAY) {
+ commitUpdate()
+ }
+ }
+
+ override fun start() {
+ super.start()
+ stateDispatcher.registerListener(defaultFlagsChangedCallback)
+ }
+
+ override fun destroy() {
+ super.destroy()
+ stateDispatcher.unregisterListener(defaultFlagsChangedCallback)
+ }
+
+ override fun commitUpdate() {
+ if (flags != lastSentFlags) {
+ stateDispatcher.dispatchSysUIStateChange(flags, displayId)
+ lastSentFlags = flags
+ }
+ }
+
+ override val flags: Long
+ get() = override.applyTo(defaultDisplayState.flags)
+
+ override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
+ val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
+ override.setFlag(flag, toSet)
+ return this
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(displayId: Int): SysUIStateOverride
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index ed190a14e680..71cb74543485 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -16,102 +16,153 @@
package com.android.systemui.model
import android.util.Log
+import android.view.Display
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.model.SysUiState.SysUiStateCallback
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import dalvik.annotation.optimization.NeverCompile
import java.io.PrintWriter
+import java.lang.Long.bitCount
+import javax.inject.Inject
/** Contains sysUi state flags and notifies registered listeners whenever changes happen. */
-@SysUISingleton
-class SysUiState(
- private val displayTracker: DisplayTracker,
+interface SysUiState : Dumpable {
+ /**
+ * Add listener to be notified of changes made to SysUI state.
+ *
+ * The callback will also be called as part of this function.
+ */
+ fun addCallback(callback: SysUiStateCallback)
+
+ /** Removes a callback for state changes. */
+ fun removeCallback(callback: SysUiStateCallback)
+
+ /** Returns whether a flag is enabled in this state. */
+ fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
+ return (flags and flag) != 0L
+ }
+
+ /** Returns the current sysui state flags. */
+ val flags: Long
+
+ /** Methods to this call can be chained together before calling [commitUpdate]. */
+ fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState
+
+ /** Call to save all the flags updated from [setFlag]. */
+ @Deprecated("Each SysUIState instance is now display specific. Just use commitUpdate()")
+ fun commitUpdate(displayId: Int)
+
+ /** Call to save all the flags updated from [setFlag]. */
+ fun commitUpdate()
+
+ /** Callback to be notified whenever system UI state flags are changed. */
+ fun interface SysUiStateCallback {
+
+ /** To be called when any SysUiStateFlag gets updated for a specific [displayId]. */
+ fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long, displayId: Int)
+ }
+
+ /**
+ * Destroys an instance. It shouldn't be used anymore afterwards.
+ *
+ * This is mainly used to clean up instances associated with displays that are removed.
+ */
+ fun destroy()
+
+ /** Initializes the state after construction. */
+ fun start()
+
+ /** The display ID this instances is associated with */
+ val displayId: Int
+
+ companion object {
+ const val DEBUG: Boolean = false
+ }
+}
+
+private const val TAG = "SysUIState"
+
+open class SysUiStateImpl
+@AssistedInject
+constructor(
+ @Assisted override val displayId: Int,
private val sceneContainerPlugin: SceneContainerPlugin?,
-) : Dumpable {
+ private val dumpManager: DumpManager,
+ private val stateDispatcher: SysUIStateDispatcher,
+) : SysUiState {
+
+ private val debugName
+ get() = "SysUiStateImpl-ForDisplay=$displayId"
+
+ override fun start() {
+ dumpManager.registerNormalDumpable(debugName, this)
+ }
+
/** Returns the current sysui state flags. */
@get:SystemUiStateFlags
@SystemUiStateFlags
- var flags: Long = 0
- private set
+ override val flags: Long
+ get() = _flags
- private val callbacks: MutableList<SysUiStateCallback> = ArrayList()
- private var flagsToSet: Long = 0
- private var flagsToClear: Long = 0
+ private var _flags: Long = 0
+ private val stateChange = StateChange()
/**
* Add listener to be notified of changes made to SysUI state. The callback will also be called
* as part of this function.
+ *
+ * Note that the listener would receive updates for all displays.
*/
- fun addCallback(callback: SysUiStateCallback) {
- callbacks.add(callback)
- callback.onSystemUiStateChanged(flags)
+ override fun addCallback(callback: SysUiStateCallback) {
+ stateDispatcher.registerListener(callback)
+ callback.onSystemUiStateChanged(flags, displayId)
}
/** Callback will no longer receive events on state change */
- fun removeCallback(callback: SysUiStateCallback) {
- callbacks.remove(callback)
- }
-
- fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
- return (flags and flag) != 0L
+ override fun removeCallback(callback: SysUiStateCallback) {
+ stateDispatcher.unregisterListener(callback)
}
/** Methods to this call can be chained together before calling [.commitUpdate]. */
- fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
- var enabled = enabled
- val overrideOrNull = sceneContainerPlugin?.flagValueOverride(flag)
- if (overrideOrNull != null && enabled != overrideOrNull) {
- if (DEBUG) {
- Log.d(
- TAG,
- "setFlag for flag $flag and value $enabled overridden to $overrideOrNull by scene container plugin",
- )
- }
-
- enabled = overrideOrNull
- }
-
- if (enabled) {
- flagsToSet = flagsToSet or flag
- } else {
- flagsToClear = flagsToClear or flag
+ override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
+ if (ShadeWindowGoesAround.isEnabled && bitCount(flag) > 1) {
+ error("Flags should be a single bit.")
}
+ val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
+ stateChange.setFlag(flag, toSet)
return this
}
- /** Call to save all the flags updated from [.setFlag]. */
- fun commitUpdate(displayId: Int) {
- updateFlags(displayId)
- flagsToSet = 0
- flagsToClear = 0
+ @Deprecated(
+ "Each SysUIState instance is now display specific. Just use commitUpdate.",
+ ReplaceWith("commitUpdate()"),
+ )
+ override fun commitUpdate(displayId: Int) {
+ commitUpdate()
}
- private fun updateFlags(displayId: Int) {
- if (displayId != displayTracker.defaultDisplayId) {
- // Ignore non-default displays for now
- Log.w(TAG, "Ignoring flag update for display: $displayId", Throwable())
- return
- }
-
- var newState = flags
- newState = newState or flagsToSet
- newState = newState and flagsToClear.inv()
+ override fun commitUpdate() {
+ val newState = stateChange.applyTo(flags)
notifyAndSetSystemUiStateChanged(newState, flags)
+ stateChange.clear()
}
/** Notify all those who are registered that the state has changed. */
private fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
- if (DEBUG) {
- Log.d(TAG, "SysUiState changed: old=$oldFlags new=$newFlags")
+ if (SysUiState.DEBUG) {
+ Log.d(TAG, "SysUiState changed for displayId=$displayId: old=$oldFlags new=$newFlags")
}
if (newFlags != oldFlags) {
- callbacks.forEach { callback: SysUiStateCallback ->
- callback.onSystemUiStateChanged(newFlags)
- }
-
- flags = newFlags
+ _flags = newFlags
+ stateDispatcher.dispatchSysUIStateChange(newFlags, displayId)
}
}
@@ -125,16 +176,66 @@ class SysUiState(
pw.println(QuickStepContract.isBackGestureDisabled(flags, false /* forTrackpad */))
pw.print(" assistantGestureDisabled=")
pw.println(QuickStepContract.isAssistantGestureDisabled(flags))
+ pw.print(" pendingStateChanges=")
+ pw.println(stateChange.toString())
}
- /** Callback to be notified whenever system UI state flags are changed. */
- interface SysUiStateCallback {
- /** To be called when any SysUiStateFlag gets updated */
- fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long)
+ override fun destroy() {
+ dumpManager.unregisterDumpable(debugName)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ /** Creates a new instance of [SysUiStateImpl] for a given [displayId]. */
+ fun create(displayId: Int): SysUiStateImpl
}
companion object {
private val TAG: String = SysUiState::class.java.simpleName
- const val DEBUG: Boolean = false
+ }
+}
+
+/** Returns the flag value taking into account [SceneContainerPlugin] potential overrides. */
+fun flagWithOptionalOverrides(
+ flag: Long,
+ enabled: Boolean,
+ displayId: Int,
+ sceneContainerPlugin: SceneContainerPlugin?,
+): Boolean {
+ var toSet = enabled
+ val overrideOrNull = sceneContainerPlugin?.flagValueOverride(flag = flag, displayId = displayId)
+ if (overrideOrNull != null && toSet != overrideOrNull) {
+ if (SysUiState.DEBUG) {
+ Log.d(
+ TAG,
+ "setFlag for flag $flag and value $toSet overridden to " +
+ "$overrideOrNull by scene container plugin",
+ )
+ }
+
+ toSet = overrideOrNull
+ }
+ return toSet
+}
+
+/** Creates and destroy instances of [SysUiState] */
+@SysUISingleton
+class SysUIStateInstanceProvider
+@Inject
+constructor(
+ private val factory: SysUiStateImpl.Factory,
+ private val overrideFactory: SysUIStateOverride.Factory,
+) : PerDisplayInstanceProviderWithTeardown<SysUiState> {
+ override fun createInstance(displayId: Int): SysUiState {
+ return if (displayId == Display.DEFAULT_DISPLAY) {
+ factory.create(displayId)
+ } else {
+ overrideFactory.create(displayId)
+ }
+ .apply { start() }
+ }
+
+ override fun destroyInstance(instance: SysUiState) {
+ instance.destroy()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
index e293e202633e..3f804f768d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
@@ -42,7 +42,9 @@ object ModesUi {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, Flags.FLAG_MODES_UI)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
index 032b0aca1770..44a3d77a6818 100644
--- a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
@@ -42,8 +42,8 @@ object ModesUiIcons {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() =
- RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 5fa0095d2329..50d0a459da66 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -18,7 +18,6 @@ package com.android.systemui.navigationbar;
import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
-import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
import static com.android.wm.shell.Flags.enableTaskbarOnPhones;
@@ -37,6 +36,7 @@ import android.view.Display;
import android.view.IWindowManager;
import android.view.View;
import android.view.WindowManagerGlobal;
+import android.window.DesktopExperienceFlags;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -285,7 +285,7 @@ public class NavigationBarControllerImpl implements
@Override
public void onDisplayAddSystemDecorations(int displayId) {
- if (enableDisplayContentModeManagement()) {
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
mHasNavBar.put(displayId, true);
}
Display display = mDisplayManager.getDisplay(displayId);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index c4d847f18269..efed260b4c99 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -44,7 +44,6 @@ import android.app.StatusBarManager.WindowVisibleState;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
import android.os.Handler;
@@ -503,9 +502,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher) {
- // Count imperceptible changes as visible so we transition taskbar out quickly.
- final boolean isImeVisible = mNavBarHelper.isImeVisible(vis)
- || (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0;
+ final boolean isImeVisible = mNavBarHelper.isImeVisible(vis);
final int flags = Utilities.updateNavbarFlagsFromIme(mNavbarFlags, backDisposition,
isImeVisible, showImeSwitcher);
if (flags == mNavbarFlags) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f44c2c01951c..237ec7cfce7f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -355,11 +355,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final SysUiState.SysUiStateCallback mSysUiStateCallback =
new SysUiState.SysUiStateCallback() {
- @Override
- public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) {
- mSysUiFlags = sysUiFlags;
- }
- };
+ @Override
+ public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags,
+ int displayId) {
+ mSysUiFlags = sysUiFlags;
+ }
+ };
private final Consumer<Boolean> mOnIsInPipStateChangedListener =
(isInPip) -> mIsInPip = isInPip;
@@ -1128,6 +1129,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
} else if (mAllowGesture || mLogGesture) {
+ boolean mLastFrameThresholdCrossed = mThresholdCrossed;
if (!mThresholdCrossed) {
mEndPoint.x = (int) ev.getX();
mEndPoint.y = (int) ev.getY();
@@ -1180,9 +1182,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
return;
} else if (dx > dy && dx > mTouchSlop) {
if (mAllowGesture) {
- if (mBackAnimation != null) {
- mBackAnimation.onThresholdCrossed();
- } else {
+ if (mBackAnimation == null) {
pilferPointers();
}
mThresholdCrossed = true;
@@ -1197,6 +1197,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
// forward touch
mEdgeBackPlugin.onMotionEvent(ev);
dispatchToBackAnimation(ev);
+ if (mBackAnimation != null && mThresholdCrossed && !mLastFrameThresholdCrossed) {
+ mBackAnimation.onThresholdCrossed();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d8fc52bcc55a..8dc27bf4ac3e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -162,10 +162,6 @@ constructor(
): Boolean {
return this@NoteTaskInitializer.handleKeyGestureEvent(event)
}
-
- override fun isKeyGestureSupported(gestureType: Int): Boolean {
- return this@NoteTaskInitializer.isKeyGestureSupported(gestureType)
- }
}
/**
@@ -225,10 +221,6 @@ constructor(
return true
}
- private fun isKeyGestureSupported(gestureType: Int): Boolean {
- return gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
- }
-
companion object {
val MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout().toLong()
val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong()
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index 43bd6aa37b5a..faa77e51ec24 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -24,7 +24,7 @@ import android.content.IntentFilter
import android.os.PowerManager
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.power.shared.model.DozeScreenStateModel
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index 297c6af5a4a7..f368c53c5b39 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -61,6 +61,11 @@ data class WakefulnessModel(
(lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
}
+ fun isAwakeFromMotionOrLift(): Boolean {
+ return isAwake() &&
+ (lastWakeReason == WakeSleepReason.MOTION || lastWakeReason == WakeSleepReason.LIFT)
+ }
+
override fun logDiffs(prevVal: WakefulnessModel, row: TableRowLogger) {
row.logChange(columnName = "wakefulness", value = toString())
}
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
index 694b525e7bc1..ea2bf6a44884 100644
--- a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
@@ -48,7 +48,7 @@ public class SystemProcessCondition extends Condition {
}
@Override
- protected int getStartStrategy() {
+ public int getStartStrategy() {
return START_EAGERLY;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index fa987ddbe266..9fc1b7aea687 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -255,7 +255,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
* @return size in pixels of QQS
*/
public int getQqsHeight() {
- SceneContainerFlag.assertInNewMode();
+ SceneContainerFlag.unsafeAssertInNewMode();
return mHeader.getMeasuredHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 5e7e0c97a147..05a60a6db31e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -58,11 +58,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
@@ -101,6 +103,7 @@ import com.android.mechanics.GestureContext
import com.android.systemui.Dumpable
import com.android.systemui.Flags
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.brightness.ui.compose.ContainerColors
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyboard.shortcut.ui.composable.InteractionsConfig
@@ -248,12 +251,25 @@ constructor(
@Composable
private fun Content() {
- PlatformTheme(isDarkTheme = true) {
+ PlatformTheme {
ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) {
- if (viewModel.isQsVisibleAndAnyShadeExpanded) {
+ // TODO(b/389985793): Make sure that there is no coroutine work or recompositions
+ // happening when alwaysCompose is true but isQsVisibleAndAnyShadeExpanded is false.
+ if (alwaysCompose || viewModel.isQsVisibleAndAnyShadeExpanded) {
Box(
modifier =
- Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
+ Modifier.thenIf(alwaysCompose) {
+ Modifier.layout { measurable, constraints ->
+ measurable.measure(constraints).run {
+ layout(width, height) {
+ if (viewModel.isQsVisibleAndAnyShadeExpanded) {
+ place(0, 0)
+ }
+ }
+ }
+ }
+ }
+ .graphicsLayer { alpha = viewModel.viewAlpha }
.thenIf(notificationScrimClippingParams.isEnabled) {
Modifier.notificationScrimClip {
notificationScrimClippingParams.params
@@ -331,12 +347,12 @@ constructor(
}
SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
- scene(QuickSettings) {
+ scene(QuickSettings, alwaysCompose = alwaysCompose) {
LaunchedEffect(Unit) { viewModel.onQSOpen() }
Element(QuickSettings.rootElementKey, Modifier) { QuickSettingsElement() }
}
- scene(QuickQuickSettings) {
+ scene(QuickQuickSettings, alwaysCompose = alwaysCompose) {
LaunchedEffect(Unit) { viewModel.onQQSOpen() }
// Cannot pass the element modifier in because the top element has a `testTag`
// and this would overwrite it.
@@ -401,7 +417,7 @@ constructor(
}
override fun isCustomizing(): Boolean {
- return viewModel.containerViewModel.editModeViewModel.isEditing.value
+ return viewModel.isEditing
}
override fun closeCustomizer() {
@@ -626,7 +642,21 @@ constructor(
) {
val Tiles =
@Composable {
- QuickQuickSettings(viewModel = viewModel.quickQuickSettingsViewModel)
+ QuickQuickSettings(
+ viewModel = viewModel.quickQuickSettingsViewModel,
+ listening = {
+ /*
+ * When always compose is false, this will always be true, and we'll be
+ * listening whenever this is composed.
+ * When always compose is true, we listen if we are visible and not
+ * fully expanded
+ */
+ !alwaysCompose ||
+ (viewModel.isQsVisibleAndAnyShadeExpanded &&
+ viewModel.expansionState.progress < 1f &&
+ !viewModel.isEditing)
+ },
+ )
}
val Media =
@Composable {
@@ -713,11 +743,23 @@ constructor(
)
val BrightnessSlider =
@Composable {
- BrightnessSliderContainer(
- viewModel = containerViewModel.brightnessSliderViewModel,
- modifier =
- Modifier.systemGestureExclusionInShade().fillMaxWidth(),
- )
+ Box(
+ Modifier.systemGestureExclusionInShade(
+ enabled = {
+ layoutState.transitionState is TransitionState.Idle
+ }
+ )
+ ) {
+ BrightnessSliderContainer(
+ viewModel = containerViewModel.brightnessSliderViewModel,
+ containerColors =
+ ContainerColors(
+ Color.Transparent,
+ ContainerColors.defaultContainerColor,
+ ),
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
}
val TileGrid =
@Composable {
@@ -726,6 +768,19 @@ constructor(
TileGrid(
viewModel = containerViewModel.tileGridViewModel,
modifier = Modifier.fillMaxWidth(),
+ listening = {
+ /*
+ * When always compose is false, this will always be true,
+ * and we'll be listening whenever this is composed.
+ * When always compose is true, we look a the second
+ * condition and we'll listen if QS is visible AND we are
+ * not fully collapsed.
+ */
+ !alwaysCompose ||
+ (viewModel.isQsVisibleAndAnyShadeExpanded &&
+ viewModel.expansionState.progress > 0f &&
+ !viewModel.isEditing)
+ },
)
}
}
@@ -830,6 +885,7 @@ constructor(
println("qqsPositionOnScreen", rect)
}
println("QQS visible", qqsVisible.value)
+ println("Always composed", alwaysCompose)
if (::viewModel.isInitialized) {
printSection("View Model") { viewModel.dump(this@run, args) }
}
@@ -1177,3 +1233,6 @@ private fun interactionsConfig() =
// we are OK using this as our content is clipped and all corner radius are larger than this
surfaceCornerRadius = 28.dp,
)
+
+private inline val alwaysCompose
+ get() = Flags.alwaysComposeQsUiFragment()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 767acc5fa76a..b61fa9cfe264 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -35,7 +35,6 @@ import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -307,6 +306,12 @@ constructor(
val animateTilesExpansion: Boolean
get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
+ val isEditing by
+ hydrator.hydratedStateOf(
+ traceName = "isEditing",
+ source = containerViewModel.editModeViewModel.isEditing,
+ )
+
private val inFirstPage: Boolean
get() = inFirstPageViewModel.inFirstPage
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
index 446be9b9ebcb..59844c7ae664 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
@@ -85,6 +85,7 @@ constructor(
LargeStaticTile(
uiState = viewModel.uiState,
+ iconProvider = viewModel.iconProvider,
modifier =
Modifier.width(
dimensionResource(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
index c756adc07ba4..dd281ccc9c50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
@@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.toIconProvider
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
@@ -58,6 +59,8 @@ constructor(
val uiState by derivedStateOf { state.toUiState(dialogContext.resources) }
+ val iconProvider by derivedStateOf { state.toIconProvider() }
+
override suspend fun onActivated(): Nothing {
withContext(backgroundDispatcher) {
tileData.icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt
index 3afaef5ea6a1..6865e0ee79f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt
@@ -81,7 +81,9 @@ object QsDetailedView {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/** Returns a developer-readable string that describes the current requirement list. */
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
index bd9d70c13572..eb99fec7a0a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.footer.data.repository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.FgsManagerController
import javax.inject.Inject
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 2670787909b4..c45fa973f973 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
@@ -18,6 +18,7 @@ package com.android.systemui.qs.footer.ui.viewmodel
import android.annotation.AttrRes
import android.annotation.ColorInt
+import androidx.compose.runtime.Stable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -25,6 +26,7 @@ import com.android.systemui.common.shared.model.Icon
* A ViewModel for a simple footer actions button. This is used for the user switcher, settings and
* power buttons.
*/
+@Stable
data class FooterActionsButtonViewModel(
val id: Int,
val icon: Icon,
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 9546e355dca2..aea280cc9f91 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
@@ -75,6 +75,7 @@ class FooterActionsViewModel(
/** The model for the power button. */
val power: Flow<FooterActionsButtonViewModel?>,
+ val initialPower: () -> FooterActionsButtonViewModel?,
/**
* Observe the device monitoring dialog requests and show the dialog accordingly. This function
@@ -181,7 +182,7 @@ class FooterActionsViewModel(
fun createFooterActionsViewModel(
@ShadeDisplayAware appContext: Context,
footerActionsInteractor: FooterActionsInteractor,
- shadeMode: Flow<ShadeMode>,
+ shadeMode: StateFlow<ShadeMode>,
falsingManager: FalsingManager,
globalActionsDialogLite: GlobalActionsDialogLite,
activityStarter: ActivityStarter,
@@ -291,6 +292,12 @@ fun createFooterActionsViewModel(
settings = settings,
power = power,
observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests,
+ initialPower =
+ if (showPowerButton) {
+ { powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked, shadeMode.value) }
+ } else {
+ { null }
+ },
)
}
@@ -411,20 +418,28 @@ fun powerButtonViewModel(
shadeMode: Flow<ShadeMode>,
): Flow<FooterActionsButtonViewModel?> {
return shadeMode.map { mode ->
- val isDualShade = mode is ShadeMode.Dual
- FooterActionsButtonViewModel(
- id = R.id.pm_lite,
- Icon.Resource(
- android.R.drawable.ic_lock_power_off,
- ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
- ),
- iconTint =
- Utils.getColorAttrDefaultColor(
- qsThemedContext,
- if (isDualShade) R.attr.onShadeInactiveVariant else R.attr.onShadeActive,
- ),
- backgroundColor = if (isDualShade) R.attr.shadeInactive else R.attr.shadeActive,
- onPowerButtonClicked,
- )
+ powerButtonViewModel(qsThemedContext, onPowerButtonClicked, mode)
}
}
+
+fun powerButtonViewModel(
+ qsThemedContext: Context,
+ onPowerButtonClicked: (Expandable) -> Unit,
+ shadeMode: ShadeMode,
+): FooterActionsButtonViewModel {
+ val isDualShade = shadeMode is ShadeMode.Dual
+ return FooterActionsButtonViewModel(
+ id = R.id.pm_lite,
+ Icon.Resource(
+ android.R.drawable.ic_lock_power_off,
+ ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
+ ),
+ iconTint =
+ Utils.getColorAttrDefaultColor(
+ qsThemedContext,
+ if (isDualShade) R.attr.onShadeInactiveVariant else R.attr.onShadeActive,
+ ),
+ backgroundColor = if (isDualShade) R.attr.shadeInactive else R.attr.shadeActive,
+ onPowerButtonClicked,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
index 185ea93387a3..1fb884de620f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
@@ -27,7 +27,17 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
/** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */
interface GridLayout {
- @Composable fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier)
+
+ /**
+ * [listening] can be used to compose the grid but limit when tiles should be listening. It
+ * should be a function tracking a snapshot state.
+ */
+ @Composable
+ fun ContentScope.TileGrid(
+ tiles: List<TileViewModel>,
+ modifier: Modifier,
+ listening: () -> Boolean,
+ )
@Composable
fun EditTileGrid(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index a07120629d2b..a7ebb2289814 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -32,7 +32,6 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
@@ -43,6 +42,7 @@ import androidx.compose.ui.res.integerResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ContentScope
import com.android.compose.modifiers.padding
+import com.android.systemui.common.ui.compose.PagerDots
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.development.ui.compose.BuildNumber
import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel
@@ -65,17 +65,16 @@ constructor(
@PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout,
) : GridLayout by delegateGridLayout {
@Composable
- override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
+ override fun ContentScope.TileGrid(
+ tiles: List<TileViewModel>,
+ modifier: Modifier,
+ listening: () -> Boolean,
+ ) {
val viewModel =
rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") {
viewModelFactory.create()
}
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
val columns = viewModel.columns
val rows = integerResource(R.integer.quick_settings_paginated_grid_num_rows)
@@ -86,6 +85,19 @@ constructor(
val pagerState = rememberPagerState(0) { pages.size }
+ LaunchedEffect(listening, pagerState) {
+ snapshotFlow { listening() }
+ .collect {
+ // Whenever we go from not listening to listening, we should be in the first
+ // page. If we did this when going from listening to not listening, opening
+ // edit mode in second page will cause it to go to first page during the
+ // transition.
+ if (listening()) {
+ pagerState.scrollToPage(0)
+ }
+ }
+ }
+
// Used to track if this is currently in the first page or not, for animations
LaunchedEffect(key1 = pagerState) {
snapshotFlow { pagerState.currentPage == 0 }.collect { viewModel.inFirstPage = it }
@@ -122,7 +134,7 @@ constructor(
) {
val page = pages[it]
- with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier) }
+ with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier, listening) }
}
FooterBar(
buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index cdc03bb9be35..d20b360756d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -18,7 +18,6 @@ package com.android.systemui.qs.panels.ui.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -41,6 +40,7 @@ import com.android.systemui.res.R
fun ContentScope.QuickQuickSettings(
viewModel: QuickQuickSettingsViewModel,
modifier: Modifier = Modifier,
+ listening: () -> Boolean,
) {
val sizedTiles = viewModel.tileViewModels
@@ -51,11 +51,6 @@ fun ContentScope.QuickQuickSettings(
val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
val columns = viewModel.columns
Box(modifier = modifier) {
GridAnchor()
@@ -91,4 +86,6 @@ fun ContentScope.QuickQuickSettings(
}
}
}
+
+ TileListener(tiles, listening)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
index 701f44e9981c..d40ecc9565ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
@@ -61,6 +61,9 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
DisposableEffect(Unit) { onDispose { detailsViewModel.closeDetailedView() } }
+ val title = tileDetailedViewModel.title
+ val subTitle = tileDetailedViewModel.subTitle
+
Column(
modifier =
modifier
@@ -90,7 +93,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
)
}
Text(
- text = tileDetailedViewModel.getTitle(),
+ text = title,
modifier = Modifier.align(Alignment.CenterVertically),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge,
@@ -110,7 +113,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
}
}
Text(
- text = tileDetailedViewModel.getSubTitle(),
+ text = subTitle,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleSmall,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
index fd10f917106b..1858825f91ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
@@ -22,9 +22,13 @@ import com.android.compose.animation.scene.ContentScope
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
@Composable
-fun ContentScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) {
+fun ContentScope.TileGrid(
+ viewModel: TileGridViewModel,
+ modifier: Modifier = Modifier,
+ listening: () -> Boolean = { true },
+) {
val gridLayout = viewModel.gridLayout
val tiles = viewModel.tileViewModels
- with(gridLayout) { TileGrid(tiles, modifier) }
+ with(gridLayout) { TileGrid(tiles, modifier, listening) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt
new file mode 100644
index 000000000000..6fb8f1bb79a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.panels.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.snapshotFlow
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+
+/**
+ * Use to have tiles start/stop listening when this is composed. Additionally, the listening state
+ * will be gated by [listeningEnabled].
+ */
+@Composable
+fun TileListener(tiles: List<TileViewModel>, listeningEnabled: () -> Boolean) {
+ LaunchedEffect(tiles) {
+ val token = Any()
+ try {
+ snapshotFlow { listeningEnabled() }
+ .collect { listening ->
+ tiles.forEach {
+ if (listening) {
+ it.startListening(token)
+ } else {
+ it.stopListening(token)
+ }
+ }
+ }
+ } finally {
+ tiles.forEach { it.stopListening(token) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 85658bb71c8b..50012abc69d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -26,12 +26,14 @@ import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.Image
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -39,6 +41,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
@@ -49,8 +52,16 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorProducer
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
@@ -60,7 +71,7 @@ import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.toggleableState
-import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.modifiers.size
@@ -73,6 +84,9 @@ import com.android.systemui.common.ui.compose.load
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TILE_INITIAL_DELAY_MILLIS
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TILE_MARQUEE_ITERATIONS
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileLabelBlurWidth
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
import com.android.systemui.qs.ui.compose.borderOnFocus
@@ -104,30 +118,31 @@ fun LargeTileContent(
val focusBorderColor = MaterialTheme.colorScheme.secondary
Box(
modifier =
- Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
- Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
- .clip(iconShape)
- .verticalSquish(squishiness)
- .drawBehind { drawRect(animatedBackgroundColor) }
- .combinedClickable(
- onClick = toggleClick!!,
- onLongClick = onLongClick,
- onLongClickLabel = longPressLabel,
- hapticFeedbackEnabled = !Flags.msdlFeedback(),
- )
- .thenIf(accessibilityUiState != null) {
- Modifier.semantics {
- accessibilityUiState as AccessibilityUiState
- contentDescription = accessibilityUiState.contentDescription
- stateDescription = accessibilityUiState.stateDescription
- accessibilityUiState.toggleableState?.let {
- toggleableState = it
+ Modifier.size(CommonTileDefaults.ToggleTargetSize)
+ .clip(iconShape)
+ .verticalSquish(squishiness)
+ .drawBehind { drawRect(animatedBackgroundColor) }
+ .thenIf(toggleClick != null) {
+ Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
+ .combinedClickable(
+ onClick = toggleClick!!,
+ onLongClick = onLongClick,
+ onLongClickLabel = longPressLabel,
+ hapticFeedbackEnabled = !Flags.msdlFeedback(),
+ )
+ .thenIf(accessibilityUiState != null) {
+ Modifier.semantics {
+ accessibilityUiState as AccessibilityUiState
+ contentDescription = accessibilityUiState.contentDescription
+ stateDescription = accessibilityUiState.stateDescription
+ accessibilityUiState.toggleableState?.let {
+ toggleableState = it
+ }
+ role = Role.Switch
}
- role = Role.Switch
- }
- .sysuiResTag(TEST_TAG_TOGGLE)
- }
- }
+ .sysuiResTag(TEST_TAG_TOGGLE)
+ }
+ }
) {
SmallTileContent(
iconProvider = iconProvider,
@@ -167,18 +182,15 @@ fun LargeTileLabels(
val animatedSecondaryLabelColor by
animateColorAsState(colors.secondaryLabel, label = "QSTileSecondaryLabelColor")
Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) {
- BasicText(
- label,
+ TileLabel(
+ text = label,
style = MaterialTheme.typography.labelLarge,
color = { animatedLabelColor },
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
)
if (!TextUtils.isEmpty(secondaryLabel)) {
- BasicText(
+ TileLabel(
secondaryLabel ?: "",
color = { animatedSecondaryLabelColor },
- maxLines = 1,
style = MaterialTheme.typography.bodyMedium,
modifier =
Modifier.thenIf(
@@ -194,9 +206,9 @@ fun LargeTileLabels(
@Composable
fun SmallTileContent(
- modifier: Modifier = Modifier,
iconProvider: Context.() -> Icon,
color: Color,
+ modifier: Modifier = Modifier,
size: () -> Dp = { CommonTileDefaults.IconSize },
animateToEnd: Boolean = false,
) {
@@ -212,31 +224,39 @@ fun SmallTileContent(
}
}
if (loadedDrawable is Animatable) {
+ // Skip initial animation, icons should animate only as the state change
+ // and not when first composed
+ var shouldSkipInitialAnimation by remember { mutableStateOf(true) }
+ LaunchedEffect(Unit) { shouldSkipInitialAnimation = animateToEnd }
+
val painter =
when (icon) {
is Icon.Resource -> {
val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
key(icon) {
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) { atEnd = true }
- rememberAnimatedVectorPainter(
- animatedImageVector = image,
- atEnd = atEnd,
- )
- }
+ var atEnd by remember(icon) { mutableStateOf(shouldSkipInitialAnimation) }
+ LaunchedEffect(key1 = icon.res) { atEnd = true }
+
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
}
}
is Icon.Loaded -> {
- LaunchedEffect(loadedDrawable) {
+ val painter = rememberDrawablePainter(loadedDrawable)
+
+ // rememberDrawablePainter automatically starts the animation. Using
+ // SideEffect here to immediately stop it if needed
+ DisposableEffect(painter) {
if (loadedDrawable is AnimatedVectorDrawable) {
loadedDrawable.forceAnimationOnUI()
}
+ if (shouldSkipInitialAnimation) {
+ loadedDrawable.stop()
+ }
+ onDispose {}
}
- rememberDrawablePainter(loadedDrawable)
+
+ painter
}
}
@@ -251,6 +271,45 @@ fun SmallTileContent(
}
}
+@Composable
+private fun TileLabel(
+ text: String,
+ color: ColorProducer,
+ style: TextStyle,
+ modifier: Modifier = Modifier,
+) {
+ BasicText(
+ text = text,
+ color = color,
+ style = style,
+ maxLines = 1,
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+ .drawWithContent {
+ drawContent()
+ // Draw a blur over the end of the text
+ val edgeWidthPx = TileLabelBlurWidth.toPx()
+ drawRect(
+ topLeft = Offset(size.width - edgeWidthPx, 0f),
+ size = Size(edgeWidthPx, size.height),
+ brush =
+ Brush.horizontalGradient(
+ colors = listOf(Color.Transparent, Color.Black),
+ startX = size.width,
+ endX = size.width - edgeWidthPx,
+ ),
+ blendMode = BlendMode.DstIn,
+ )
+ }
+ .basicMarquee(
+ iterations = TILE_MARQUEE_ITERATIONS,
+ initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
+ ),
+ )
+}
+
object CommonTileDefaults {
val IconSize = 32.dp
val LargeTileIconSize = 28.dp
@@ -258,9 +317,13 @@ object CommonTileDefaults {
val SideIconHeight = 20.dp
val ToggleTargetSize = 56.dp
val TileHeight = 72.dp
- val TilePadding = 8.dp
+ val TileStartPadding = 8.dp
+ val TileEndPadding = 16.dp
val TileArrangementPadding = 6.dp
val InactiveCornerRadius = 50.dp
+ val TileLabelBlurWidth = 32.dp
+ const val TILE_MARQUEE_ITERATIONS = 1
+ const val TILE_INITIAL_DELAY_MILLIS = 2000
@Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index ddadb8879f07..ccbd8fdbe00c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -22,6 +22,7 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -62,6 +63,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -78,6 +80,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -87,6 +90,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.graphics.Color
@@ -111,7 +115,9 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastMap
+import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory
import com.android.compose.modifiers.height
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.common.ui.compose.load
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
@@ -129,6 +135,7 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaul
import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_SPEED
import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AvailableTilesGridMinHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.GridBackgroundCornerRadius
import com.android.systemui.qs.panels.ui.compose.selection.InteractiveTileContainer
import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState
import com.android.systemui.qs.panels.ui.compose.selection.ResizingState
@@ -140,6 +147,7 @@ import com.android.systemui.qs.panels.ui.compose.selection.TileState
import com.android.systemui.qs.panels.ui.compose.selection.rememberResizingState
import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState
import com.android.systemui.qs.panels.ui.compose.selection.selectableTile
+import com.android.systemui.qs.panels.ui.model.AvailableTileGridCell
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.SpacerGridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
@@ -160,14 +168,27 @@ object TileType
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
-
+ val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer
TopAppBar(
- colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
- title = { Text(text = stringResource(id = R.string.qs_edit)) },
+ colors =
+ TopAppBarDefaults.topAppBarColors(
+ containerColor = Color.Transparent,
+ titleContentColor = MaterialTheme.colorScheme.onSurface,
+ ),
+ title = {
+ Text(
+ text = stringResource(id = R.string.qs_edit),
+ modifier = Modifier.padding(start = 24.dp),
+ )
+ },
navigationIcon = {
- IconButton(onClick = onStopEditing) {
+ IconButton(
+ onClick = onStopEditing,
+ modifier = Modifier.drawBehind { drawCircle(primaryContainerColor) },
+ ) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
+ tint = MaterialTheme.colorScheme.onSurface,
contentDescription =
stringResource(id = com.android.internal.R.string.action_bar_up_description),
)
@@ -175,11 +196,19 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
},
actions = {
if (onReset != null) {
- TextButton(onClick = onReset) {
+ TextButton(
+ onClick = onReset,
+ colors =
+ ButtonDefaults.textButtonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ ),
+ ) {
Text(stringResource(id = com.android.internal.R.string.reset))
}
}
},
+ modifier = Modifier.padding(vertical = 8.dp),
)
}
@@ -212,7 +241,9 @@ fun DefaultEditTileGrid(
containerColor = Color.Transparent,
topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
) { innerPadding ->
- CompositionLocalProvider(LocalOverscrollFactory provides null) {
+ CompositionLocalProvider(
+ LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory()
+ ) {
val scrollState = rememberScrollState()
AutoScrollGrid(listState, scrollState, innerPadding)
@@ -241,7 +272,7 @@ fun DefaultEditTileGrid(
targetState = listState.dragInProgress || selectionState.selected,
label = "QSEditHeader",
contentAlignment = Alignment.Center,
- modifier = Modifier.fillMaxWidth().heightIn(min = 80.dp),
+ modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp),
) { showRemoveTarget ->
EditGridHeader {
if (showRemoveTarget) {
@@ -286,12 +317,19 @@ fun DefaultEditTileGrid(
spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
modifier = modifier.fillMaxSize(),
) {
- EditGridHeader {
- Text(text = stringResource(id = R.string.drag_to_add_tiles))
+ val availableTiles = remember {
+ mutableStateListOf<AvailableTileGridCell>().apply {
+ addAll(toAvailableTiles(listState.tiles, otherTiles))
+ }
+ }
+ LaunchedEffect(listState.tiles, otherTiles) {
+ availableTiles.apply {
+ clear()
+ addAll(toAvailableTiles(listState.tiles, otherTiles))
+ }
}
-
AvailableTileGrid(
- otherTiles,
+ availableTiles,
selectionState,
columns,
onAddTile,
@@ -357,9 +395,7 @@ private fun EditGridHeader(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
- CompositionLocalProvider(
- LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
- ) {
+ CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxWidth()) { content() }
}
}
@@ -406,6 +442,7 @@ private fun CurrentTilesGrid(
listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) }
}
+ val primaryColor = MaterialTheme.colorScheme.primary
TileLazyGrid(
state = gridState,
columns = GridCells.Fixed(columns),
@@ -414,9 +451,9 @@ private fun CurrentTilesGrid(
Modifier.fillMaxWidth()
.height { totalHeight.roundToPx() }
.border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
- shape = RoundedCornerShape((TileHeight / 2) + CurrentTilesGridPadding),
+ width = 2.dp,
+ color = primaryColor,
+ shape = RoundedCornerShape(GridBackgroundCornerRadius),
)
.dragAndDropTileList(gridState, { gridContentOffset }, listState) { spec ->
onSetTiles(currentListState.tileSpecs())
@@ -425,6 +462,13 @@ private fun CurrentTilesGrid(
.onGloballyPositioned { coordinates ->
gridContentOffset = coordinates.positionInRoot()
}
+ .drawBehind {
+ drawRoundRect(
+ primaryColor,
+ cornerRadius = CornerRadius(GridBackgroundCornerRadius.toPx()),
+ alpha = .15f,
+ )
+ }
.testTag(CURRENT_TILES_GRID_TEST_TAG),
) {
EditTiles(cells, listState, selectionState, coroutineScope, largeTilesSpan, onRemoveTile) {
@@ -444,7 +488,7 @@ private fun CurrentTilesGrid(
@Composable
private fun AvailableTileGrid(
- tiles: List<SizedTile<EditTileViewModel>>,
+ tiles: List<AvailableTileGridCell>,
selectionState: MutableSelectionState,
columns: Int,
onAddTile: (TileSpec) -> Unit,
@@ -453,9 +497,8 @@ private fun AvailableTileGrid(
// Available tiles aren't visible during drag and drop, so the row/col isn't needed
val groupedTiles =
remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
- groupAndSort(tiles.fastMap { TileGridCell(it, 0, 0) })
+ groupAndSort(tiles)
}
- val labelColors = EditModeTileDefaults.editTileColors()
// Available tiles
Column(
@@ -466,33 +509,45 @@ private fun AvailableTileGrid(
) {
groupedTiles.forEach { (category, tiles) ->
key(category) {
- Text(
- text = category.label.load() ?: "",
- fontSize = 20.sp,
- color = labelColors.label,
+ val surfaceColor = MaterialTheme.colorScheme.surface
+ Column(
+ verticalArrangement = spacedBy(16.dp),
modifier =
- Modifier.fillMaxWidth().padding(start = 16.dp, bottom = 8.dp, top = 8.dp),
- )
- tiles.chunked(columns).forEach { row ->
- Row(
- horizontalArrangement = spacedBy(TileArrangementPadding),
- modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
- ) {
- row.forEachIndexed { index, tileGridCell ->
- key(tileGridCell.tile.tileSpec) {
- AvailableTileGridCell(
- cell = tileGridCell,
- index = index,
- dragAndDropState = dragAndDropState,
- selectionState = selectionState,
- onAddTile = onAddTile,
- modifier = Modifier.weight(1f).fillMaxHeight(),
+ Modifier.drawBehind {
+ drawRoundRect(
+ surfaceColor,
+ cornerRadius = CornerRadius(GridBackgroundCornerRadius.toPx()),
+ alpha = .32f,
)
}
- }
+ .padding(16.dp),
+ ) {
+ Text(
+ text = category.label.load() ?: "",
+ fontSize = 20.sp,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp),
+ )
+ tiles.chunked(columns).forEach { row ->
+ Row(
+ horizontalArrangement = spacedBy(TileArrangementPadding),
+ modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
+ ) {
+ row.forEach { tileGridCell ->
+ key(tileGridCell.key) {
+ AvailableTileGridCell(
+ cell = tileGridCell,
+ dragAndDropState = dragAndDropState,
+ selectionState = selectionState,
+ onAddTile = onAddTile,
+ modifier = Modifier.weight(1f).fillMaxHeight(),
+ )
+ }
+ }
- // Spacers for incomplete rows
- repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) }
+ // Spacers for incomplete rows
+ repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) }
+ }
}
}
}
@@ -505,10 +560,7 @@ fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp
}
private fun GridCell.key(index: Int): Any {
- return when (this) {
- is TileGridCell -> key
- is SpacerGridCell -> index
- }
+ return if (this is TileGridCell) key else index
}
/**
@@ -687,41 +739,44 @@ private fun TileGridCell(
@Composable
private fun AvailableTileGridCell(
- cell: TileGridCell,
- index: Int,
+ cell: AvailableTileGridCell,
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
onAddTile: (TileSpec) -> Unit,
modifier: Modifier = Modifier,
) {
- val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ val stateDescription: String? =
+ if (cell.isAvailable) null
+ else stringResource(R.string.accessibility_qs_edit_tile_already_added)
+
+ val alpha by animateFloatAsState(if (cell.isAvailable) 1f else .38f)
val colors = EditModeTileDefaults.editTileColors()
- val onClick = {
- onAddTile(cell.tile.tileSpec)
- selectionState.select(cell.tile.tileSpec)
- }
// Displays the tile as an icon tile with the label underneath
Column(
horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top),
- modifier = modifier,
+ verticalArrangement = spacedBy(CommonTileDefaults.TileStartPadding, Alignment.Top),
+ modifier =
+ modifier
+ .graphicsLayer { this.alpha = alpha }
+ .semantics(mergeDescendants = true) {
+ stateDescription?.let { this.stateDescription = it }
+ },
) {
Box(Modifier.fillMaxWidth().height(TileHeight)) {
- Box(
- Modifier.fillMaxSize()
- .clickable(onClick = onClick, onClickLabel = onClickActionName)
- .semantics(mergeDescendants = true) { this.stateDescription = stateDescription }
- .dragAndDropTileSource(
+ val draggableModifier =
+ if (cell.isAvailable) {
+ Modifier.dragAndDropTileSource(
SizedTileImpl(cell.tile, cell.width),
dragAndDropState,
DragType.Add,
) {
selectionState.unSelect()
}
- .tileBackground(colors.background)
- ) {
+ } else {
+ Modifier
+ }
+ Box(draggableModifier.fillMaxSize().tileBackground(colors.background)) {
// Icon
SmallTileContent(
iconProvider = { cell.tile.icon },
@@ -733,9 +788,13 @@ private fun AvailableTileGridCell(
StaticTileBadge(
icon = Icons.Default.Add,
- contentDescription = onClickActionName,
- onClick = onClick,
- )
+ contentDescription =
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action),
+ enabled = cell.isAvailable,
+ ) {
+ onAddTile(cell.tile.tileSpec)
+ selectionState.select(cell.tile.tileSpec)
+ }
}
Box(Modifier.fillMaxSize()) {
Text(
@@ -744,7 +803,7 @@ private fun AvailableTileGridCell(
color = colors.label,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
- modifier = Modifier.align(Alignment.Center),
+ modifier = Modifier.align(Alignment.TopCenter),
)
}
}
@@ -796,7 +855,7 @@ fun EditTile(
placeable.place(startPadding.roundToInt(), 0)
}
}
- .tilePadding(),
+ .largeTilePadding(),
) {
// Icon
Box(Modifier.size(ToggleTargetSize)) {
@@ -819,9 +878,18 @@ fun EditTile(
}
}
+private fun toAvailableTiles(
+ currentTiles: List<GridCell>,
+ otherTiles: List<SizedTile<EditTileViewModel>>,
+): List<AvailableTileGridCell> {
+ return currentTiles.filterIsInstance<TileGridCell>().fastMap {
+ AvailableTileGridCell(it.tile, isAvailable = false)
+ } + otherTiles.fastMap { AvailableTileGridCell(it.tile) }
+}
+
private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float {
return (containerSize - ToggleTargetSize.roundToPx()) / 2f -
- CommonTileDefaults.TilePadding.toPx()
+ CommonTileDefaults.TileStartPadding.toPx()
}
private fun Modifier.tileBackground(color: Color): Modifier {
@@ -835,15 +903,16 @@ private object EditModeTileDefaults {
const val AUTO_SCROLL_SPEED = 2 // 2ms per pixel
val CurrentTilesGridPadding = 10.dp
val AvailableTilesGridMinHeight = 200.dp
+ val GridBackgroundCornerRadius = 42.dp
@Composable
fun editTileColors(): TileColors =
TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.surfaceVariant,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ background = LocalAndroidColorScheme.current.surfaceEffect2,
+ iconBackground = Color.Transparent,
+ label = MaterialTheme.colorScheme.onSurface,
+ secondaryLabel = MaterialTheme.colorScheme.onSurface,
+ icon = MaterialTheme.colorScheme.onSurface,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 0503049382a8..6236ada46374 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -34,6 +33,7 @@ import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
+import com.android.systemui.qs.panels.ui.compose.TileListener
import com.android.systemui.qs.panels.ui.compose.bounceableInfo
import com.android.systemui.qs.panels.ui.compose.rememberEditListState
import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
@@ -59,12 +59,11 @@ constructor(
) : PaginatableGridLayout {
@Composable
- override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
+ override fun ContentScope.TileGrid(
+ tiles: List<TileViewModel>,
+ modifier: Modifier,
+ listening: () -> Boolean,
+ ) {
val viewModel =
rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") {
viewModelFactory.create()
@@ -116,6 +115,8 @@ constructor(
)
}
}
+
+ TileListener(tiles, listening)
}
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index d73dc870756b..6bafd432669a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -20,10 +20,12 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid
import android.content.Context
import android.content.res.Resources
+import android.os.Trace
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
@@ -47,8 +49,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
@@ -58,8 +60,9 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
@@ -69,10 +72,13 @@ import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.trace
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.Expandable
import com.android.compose.animation.bounceable
+import com.android.compose.animation.rememberExpandableController
import com.android.compose.modifiers.thenIf
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -80,15 +86,19 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel
import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.panels.ui.compose.BounceableInfo
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileEndPadding
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileStartPadding
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconProvider
import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toIconProvider
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.ui.compose.borderOnFocus
@@ -117,6 +127,9 @@ fun TileLazyGrid(
)
}
+private val TileViewModel.traceName
+ get() = spec.toString().takeLast(Trace.MAX_SECTION_NAME_LEN)
+
@Composable
fun Tile(
tile: TileViewModel,
@@ -128,105 +141,116 @@ fun Tile(
modifier: Modifier = Modifier,
detailsViewModel: DetailsViewModel?,
) {
- val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
- val resources = resources()
-
- /*
- * Use produce state because [QSTile.State] doesn't have well defined equals (due to
- * inheritance). This way, even if tile.state changes, uiState may not change and lead to
- * recomposition.
- */
- val uiState by
- produceState(tile.currentState.toUiState(resources), tile, resources) {
- tile.state.collect { value = it.toUiState(resources) }
- }
+ trace(tile.traceName) {
+ val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
+ val resources = resources()
+
+ /*
+ * Use produce state because [QSTile.State] doesn't have well defined equals (due to
+ * inheritance). This way, even if tile.state changes, uiState may not change and lead to
+ * recomposition.
+ */
+ val uiState by
+ produceState(tile.currentState.toUiState(resources), tile, resources) {
+ tile.state.collect { value = it.toUiState(resources) }
+ }
- val colors = TileDefaults.getColorForState(uiState, iconOnly)
- val hapticsViewModel: TileHapticsViewModel? =
- rememberViewModel(traceName = "TileHapticsViewModel") {
- tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile)
- }
+ val icon by
+ produceState(tile.currentState.toIconProvider(), tile) {
+ tile.state.collect { value = it.toIconProvider() }
+ }
- // TODO(b/361789146): Draw the shapes instead of clipping
- val tileShape by TileDefaults.animateTileShapeAsState(uiState.state)
- val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor")
+ val colors = TileDefaults.getColorForState(uiState, iconOnly)
+ val hapticsViewModel: TileHapticsViewModel? =
+ rememberViewModel(traceName = "TileHapticsViewModel") {
+ tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile)
+ }
- TileExpandable(
- color = { animatedColor },
- shape = tileShape,
- squishiness = squishiness,
- hapticsViewModel = hapticsViewModel,
- modifier =
- modifier
- .borderOnFocus(color = MaterialTheme.colorScheme.secondary, tileShape.topEnd)
- .fillMaxWidth()
- .bounceable(
- bounceable = currentBounceableInfo.bounceable,
- previousBounceable = currentBounceableInfo.previousTile,
- nextBounceable = currentBounceableInfo.nextTile,
- orientation = Orientation.Horizontal,
- bounceEnd = currentBounceableInfo.bounceEnd,
- ),
- ) { expandable ->
- val longClick: (() -> Unit)? =
- {
- hapticsViewModel?.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.LONG_CLICKED
- )
- tile.onLongClick(expandable)
- }
- .takeIf { uiState.handlesLongClick }
- TileContainer(
- onClick = {
- var hasDetails = false
- if (QsDetailedView.isEnabled) {
- hasDetails = detailsViewModel?.onTileClicked(tile.spec) == true
- }
- if (!hasDetails) {
- // For those tile's who doesn't have a detailed view, process with their
- // `onClick` behavior.
- tile.onClick(expandable)
- hapticsViewModel?.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.CLICKED
+ // TODO(b/361789146): Draw the shapes instead of clipping
+ val tileShape by TileDefaults.animateTileShapeAsState(uiState.state)
+ val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor")
+ val animatedAlpha by animateFloatAsState(colors.alpha, label = "QSTileAlpha")
+
+ TileExpandable(
+ color = { animatedColor },
+ shape = tileShape,
+ squishiness = squishiness,
+ hapticsViewModel = hapticsViewModel,
+ modifier =
+ modifier
+ .borderOnFocus(color = MaterialTheme.colorScheme.secondary, tileShape.topEnd)
+ .fillMaxWidth()
+ .bounceable(
+ bounceable = currentBounceableInfo.bounceable,
+ previousBounceable = currentBounceableInfo.previousTile,
+ nextBounceable = currentBounceableInfo.nextTile,
+ orientation = Orientation.Horizontal,
+ bounceEnd = currentBounceableInfo.bounceEnd,
)
- if (uiState.accessibilityUiState.toggleableState != null) {
- coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
+ .graphicsLayer { alpha = animatedAlpha },
+ ) { expandable ->
+ val longClick: (() -> Unit)? =
+ {
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+ )
+ tile.onLongClick(expandable)
}
- }
- },
- onLongClick = longClick,
- uiState = uiState,
- iconOnly = iconOnly,
- ) {
- val iconProvider: Context.() -> Icon = { getTileIcon(icon = uiState.icon) }
- if (iconOnly) {
- SmallTileContent(
- iconProvider = iconProvider,
- color = colors.icon,
- modifier = Modifier.align(Alignment.Center),
- )
- } else {
- val iconShape by TileDefaults.animateIconShapeAsState(uiState.state)
- val secondaryClick: (() -> Unit)? =
- {
- hapticsViewModel?.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.CLICKED
- )
- tile.onSecondaryClick()
+ .takeIf { uiState.handlesLongClick }
+ TileContainer(
+ onClick = {
+ var hasDetails = false
+ if (QsDetailedView.isEnabled) {
+ hasDetails = detailsViewModel?.onTileClicked(tile.spec) == true
+ }
+ if (!hasDetails) {
+ // For those tile's who doesn't have a detailed view, process with their
+ // `onClick` behavior.
+ tile.onClick(expandable)
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.CLICKED
+ )
+ if (uiState.accessibilityUiState.toggleableState != null) {
+ coroutineScope.launch {
+ currentBounceableInfo.bounceable.animateBounce()
+ }
}
- .takeIf { uiState.handlesSecondaryClick }
- LargeTileContent(
- label = uiState.label,
- secondaryLabel = uiState.secondaryLabel,
- iconProvider = iconProvider,
- sideDrawable = uiState.sideDrawable,
- colors = colors,
- iconShape = iconShape,
- toggleClick = secondaryClick,
- onLongClick = longClick,
- accessibilityUiState = uiState.accessibilityUiState,
- squishiness = squishiness,
- )
+ }
+ },
+ onLongClick = longClick,
+ accessibilityUiState = uiState.accessibilityUiState,
+ iconOnly = iconOnly,
+ ) {
+ val iconProvider: Context.() -> Icon = { getTileIcon(icon = icon) }
+ if (iconOnly) {
+ SmallTileContent(
+ iconProvider = iconProvider,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ } else {
+ val iconShape by TileDefaults.animateIconShapeAsState(uiState.state)
+ val secondaryClick: (() -> Unit)? =
+ {
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.CLICKED
+ )
+ tile.onSecondaryClick()
+ }
+ .takeIf { uiState.handlesSecondaryClick }
+ LargeTileContent(
+ label = uiState.label,
+ secondaryLabel = uiState.secondaryLabel,
+ iconProvider = iconProvider,
+ sideDrawable = uiState.sideDrawable,
+ colors = colors,
+ iconShape = iconShape,
+ toggleClick = secondaryClick,
+ onLongClick = longClick,
+ accessibilityUiState = uiState.accessibilityUiState,
+ squishiness = squishiness,
+ )
+ }
}
}
}
@@ -242,8 +266,7 @@ private fun TileExpandable(
content: @Composable (Expandable) -> Unit,
) {
Expandable(
- color = color(),
- shape = shape,
+ controller = rememberExpandableController(color = color, shape = shape),
modifier = modifier.clip(shape).verticalSquish(squishiness),
useModifierBasedImplementation = true,
) {
@@ -255,7 +278,7 @@ private fun TileExpandable(
fun TileContainer(
onClick: () -> Unit,
onLongClick: (() -> Unit)?,
- uiState: TileUiState,
+ accessibilityUiState: AccessibilityUiState,
iconOnly: Boolean,
content: @Composable BoxScope.() -> Unit,
) {
@@ -266,17 +289,21 @@ fun TileContainer(
.tileCombinedClickable(
onClick = onClick,
onLongClick = onLongClick,
- uiState = uiState,
+ accessibilityUiState = accessibilityUiState,
iconOnly = iconOnly,
)
.sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
- .tilePadding(),
+ .thenIf(!iconOnly) { Modifier.largeTilePadding() }, // Icon tiles are center aligned
content = content,
)
}
@Composable
-fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) {
+fun LargeStaticTile(
+ uiState: TileUiState,
+ iconProvider: IconProvider,
+ modifier: Modifier = Modifier,
+) {
val colors = TileDefaults.getColorForState(uiState = uiState, iconOnly = false)
Box(
@@ -284,12 +311,12 @@ fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) {
.clip(TileDefaults.animateTileShapeAsState(state = uiState.state).value)
.background(colors.background)
.height(TileHeight)
- .tilePadding()
+ .largeTilePadding()
) {
LargeTileContent(
label = uiState.label,
secondaryLabel = "",
- iconProvider = { getTileIcon(icon = uiState.icon) },
+ iconProvider = { getTileIcon(icon = iconProvider) },
sideDrawable = null,
colors = colors,
squishiness = { 1f },
@@ -297,8 +324,8 @@ fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) {
}
}
-private fun Context.getTileIcon(icon: QSTile.Icon?): Icon {
- return icon?.let {
+private fun Context.getTileIcon(icon: IconProvider): Icon {
+ return icon.icon?.let {
if (it is QSTileImpl.ResourceIcon) {
Icon.Resource(it.resId, null)
} else {
@@ -311,36 +338,34 @@ fun tileHorizontalArrangement(): Arrangement.Horizontal {
return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start)
}
-fun Modifier.tilePadding(): Modifier {
- return padding(CommonTileDefaults.TilePadding)
+fun Modifier.largeTilePadding(): Modifier {
+ return padding(start = TileStartPadding, end = TileEndPadding)
}
@Composable
fun Modifier.tileCombinedClickable(
onClick: () -> Unit,
onLongClick: (() -> Unit)?,
- uiState: TileUiState,
+ accessibilityUiState: AccessibilityUiState,
iconOnly: Boolean,
): Modifier {
val longPressLabel = longPressLabel()
return combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
- onClickLabel = uiState.accessibilityUiState.clickLabel,
+ onClickLabel = accessibilityUiState.clickLabel,
onLongClickLabel = longPressLabel,
hapticFeedbackEnabled = !Flags.msdlFeedback(),
)
.semantics {
- role = uiState.accessibilityUiState.accessibilityRole
- if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) {
- uiState.accessibilityUiState.toggleableState?.let { toggleableState = it }
+ role = accessibilityUiState.accessibilityRole
+ if (accessibilityUiState.accessibilityRole == Role.Switch) {
+ accessibilityUiState.toggleableState?.let { toggleableState = it }
}
- stateDescription = uiState.accessibilityUiState.stateDescription
+ stateDescription = accessibilityUiState.stateDescription
}
.thenIf(iconOnly) {
- Modifier.semantics {
- contentDescription = uiState.accessibilityUiState.contentDescription
- }
+ Modifier.semantics { contentDescription = accessibilityUiState.contentDescription }
}
}
@@ -350,16 +375,17 @@ data class TileColors(
val label: Color,
val secondaryLabel: Color,
val icon: Color,
+ val alpha: Float = 1f,
)
private object TileDefaults {
val ActiveIconCornerRadius = 16.dp
val ActiveTileCornerRadius = 24.dp
- /** An active tile without dual target uses the active color as background */
+ /** An active icon tile uses the active color as background */
@Composable
@ReadOnlyComposable
- fun activeTileColors(): TileColors =
+ fun activeIconTileColors(): TileColors =
TileColors(
background = MaterialTheme.colorScheme.primary,
iconBackground = MaterialTheme.colorScheme.primary,
@@ -373,10 +399,10 @@ private object TileDefaults {
@ReadOnlyComposable
fun activeDualTargetTileColors(): TileColors =
TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
+ background = LocalAndroidColorScheme.current.surfaceEffect2,
iconBackground = MaterialTheme.colorScheme.primary,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ label = MaterialTheme.colorScheme.onSurface,
+ secondaryLabel = MaterialTheme.colorScheme.onSurface,
icon = MaterialTheme.colorScheme.onPrimary,
)
@@ -384,44 +410,46 @@ private object TileDefaults {
@ReadOnlyComposable
fun inactiveDualTargetTileColors(): TileColors =
TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.surfaceContainerHighest,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ background = LocalAndroidColorScheme.current.surfaceEffect2,
+ iconBackground = LocalAndroidColorScheme.current.surfaceEffect3,
+ label = MaterialTheme.colorScheme.onSurface,
+ secondaryLabel = MaterialTheme.colorScheme.onSurface,
+ icon = MaterialTheme.colorScheme.onSurface,
)
@Composable
@ReadOnlyComposable
fun inactiveTileColors(): TileColors =
TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.surfaceVariant,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ background = LocalAndroidColorScheme.current.surfaceEffect2,
+ iconBackground = Color.Transparent,
+ label = MaterialTheme.colorScheme.onSurface,
+ secondaryLabel = MaterialTheme.colorScheme.onSurface,
+ icon = MaterialTheme.colorScheme.onSurface,
)
@Composable
@ReadOnlyComposable
- fun unavailableTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surface,
- iconBackground = MaterialTheme.colorScheme.surface,
+ fun unavailableTileColors(): TileColors {
+ return TileColors(
+ background = LocalAndroidColorScheme.current.surfaceEffect2,
+ iconBackground = LocalAndroidColorScheme.current.surfaceEffect2,
label = MaterialTheme.colorScheme.onSurface,
secondaryLabel = MaterialTheme.colorScheme.onSurface,
icon = MaterialTheme.colorScheme.onSurface,
+ alpha = .38f,
)
+ }
@Composable
@ReadOnlyComposable
fun getColorForState(uiState: TileUiState, iconOnly: Boolean): TileColors {
return when (uiState.state) {
STATE_ACTIVE -> {
- if (uiState.handlesSecondaryClick && !iconOnly) {
+ if (!iconOnly) {
activeDualTargetTileColors()
} else {
- activeTileColors()
+ activeIconTileColors()
}
}
@@ -472,14 +500,15 @@ private object TileDefaults {
label = label,
)
- val corner = remember {
- object : CornerSize {
- override fun toPx(shapeSize: Size, density: Density): Float {
- return with(density) { animatedCornerRadius.toPx() }
+ return remember {
+ val corner =
+ object : CornerSize {
+ override fun toPx(shapeSize: Size, density: Density): Float {
+ return with(density) { animatedCornerRadius.toPx() }
+ }
}
- }
+ mutableStateOf(RoundedCornerShape(corner))
}
- return remember { derivedStateOf { RoundedCornerShape(corner) } }
}
}
@@ -491,5 +520,5 @@ private object TileDefaults {
@ReadOnlyComposable
private fun resources(): Resources {
LocalConfiguration.current
- return LocalContext.current.resources
+ return LocalResources.current
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 699e5f6b77e9..57f63c755b43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.panels.ui.compose.selection
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateOffset
import androidx.compose.animation.core.animateSize
import androidx.compose.animation.core.updateTransition
@@ -58,11 +59,13 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
-import com.android.compose.modifiers.size
+import com.android.compose.modifiers.thenIf
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BADGE_ANGLE_RAD
+import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeIconSize
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeSize
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeXOffset
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeYOffset
@@ -147,16 +150,15 @@ fun InteractiveTileContainer(
onClick = onClick,
)
) {
+ val size = with(LocalDensity.current) { BadgeIconSize.toDp() }
Icon(
Icons.Default.Remove,
contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimaryContainer,
modifier =
- Modifier.size(
- width = { decorationSize.width.roundToInt() },
- height = { decorationSize.height.roundToInt() },
- )
- .align(Alignment.Center)
- .graphicsLayer { this.alpha = badgeIconAlpha },
+ Modifier.size(size).align(Alignment.Center).graphicsLayer {
+ this.alpha = badgeIconAlpha
+ },
)
}
}
@@ -184,26 +186,47 @@ private fun Modifier.selectionBorder(
}
}
+/**
+ * Draws a clickable badge in the top end corner of the parent composable.
+ *
+ * The badge will fade in and fade out based on whether or not it's enabled.
+ *
+ * @param icon the [ImageVector] to display in the badge
+ * @param contentDescription the content description for the icon
+ * @param enabled Whether the badge should be visible and clickable
+ * @param onClick the callback when the badge is clicked
+ */
@Composable
-fun StaticTileBadge(icon: ImageVector, contentDescription: String?, onClick: () -> Unit) {
+fun StaticTileBadge(
+ icon: ImageVector,
+ contentDescription: String?,
+ enabled: Boolean,
+ onClick: () -> Unit,
+) {
val offset = with(LocalDensity.current) { Offset(BadgeXOffset.toPx(), BadgeYOffset.toPx()) }
+ val alpha by animateFloatAsState(if (enabled) 1f else 0f)
MinimumInteractiveSizeComponent(angle = { BADGE_ANGLE_RAD }, offset = { offset }) {
Box(
Modifier.fillMaxSize()
- .clickable(
- interactionSource = null,
- indication = null,
- onClickLabel = contentDescription,
- onClick = onClick,
- )
+ .graphicsLayer { this.alpha = alpha }
+ .thenIf(enabled) {
+ Modifier.clickable(
+ interactionSource = null,
+ indication = null,
+ onClickLabel = contentDescription,
+ onClick = onClick,
+ )
+ }
) {
- val secondaryColor = MaterialTheme.colorScheme.secondary
+ val size = with(LocalDensity.current) { BadgeIconSize.toDp() }
+ val primaryColor = MaterialTheme.colorScheme.primary
Icon(
icon,
contentDescription = contentDescription,
+ tint = MaterialTheme.colorScheme.onPrimary,
modifier =
- Modifier.size(BadgeSize).align(Alignment.Center).drawBehind {
- drawCircle(secondaryColor)
+ Modifier.size(size).align(Alignment.Center).drawBehind {
+ drawCircle(primaryColor, radius = BadgeSize.toPx() / 2)
},
)
}
@@ -214,7 +237,8 @@ fun StaticTileBadge(icon: ImageVector, contentDescription: String?, onClick: ()
private fun MinimumInteractiveSizeComponent(
angle: () -> Float,
offset: () -> Offset,
- content: @Composable BoxScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit = {},
) {
// Use a higher zIndex than the tile to draw over it, and manually create the touch target
// as we're drawing over neighbor tiles as well.
@@ -222,7 +246,8 @@ private fun MinimumInteractiveSizeComponent(
Box(
contentAlignment = Alignment.Center,
modifier =
- Modifier.zIndex(2f)
+ modifier
+ .zIndex(2f)
.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) }
.layout { measurable, constraints ->
val size = minTouchTargetSize.roundToPx()
@@ -267,7 +292,7 @@ private fun Transition<TileState>.animateColor(): State<Color> {
return animateColor { state ->
when (state) {
None -> Color.Transparent
- Removable -> MaterialTheme.colorScheme.secondary
+ Removable -> MaterialTheme.colorScheme.primaryContainer
Selected -> MaterialTheme.colorScheme.primary
}
}
@@ -315,6 +340,7 @@ private fun offsetForAngle(angle: Float, radius: Float, center: Offset): Offset
private object SelectionDefaults {
val SelectedBorderWidth = 2.dp
val BadgeSize = 24.dp
+ val BadgeIconSize = 16.sp
val BadgeXOffset = -4.dp
val BadgeYOffset = 4.dp
val ResizingPillWidth = 8.dp
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
index 360266a31be3..3ae90d2f976b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
@@ -16,14 +16,20 @@
package com.android.systemui.qs.panels.ui.compose.toolbar
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.development.ui.compose.BuildNumber
import com.android.systemui.qs.footer.ui.compose.IconButton
import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
+import com.android.systemui.qs.ui.compose.borderOnFocus
@Composable
fun Toolbar(viewModel: ToolbarViewModel, modifier: Modifier = Modifier) {
@@ -44,10 +50,23 @@ fun Toolbar(viewModel: ToolbarViewModel, modifier: Modifier = Modifier) {
Modifier.sysuiResTag("settings_button_container"),
)
- Spacer(modifier = Modifier.weight(1f))
-
- viewModel.powerButtonViewModel?.let {
- IconButton(it, useModifierBasedExpandable = true, Modifier.sysuiResTag("pm_lite"))
+ Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
+ BuildNumber(
+ viewModelFactory = viewModel.buildNumberViewModelFactory,
+ textColor = MaterialTheme.colorScheme.onSurface,
+ modifier =
+ Modifier.borderOnFocus(
+ color = MaterialTheme.colorScheme.secondary,
+ cornerSize = CornerSize(1.dp),
+ )
+ .wrapContentSize(),
+ )
}
+
+ IconButton(
+ { viewModel.powerButtonViewModel },
+ useModifierBasedExpandable = true,
+ Modifier.sysuiResTag("pm_lite"),
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index c0441f8a38a1..78fd8c0168dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -21,13 +21,13 @@ import androidx.compose.runtime.Immutable
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.CategoryAndName
/** Represents an item from a grid associated with a row and a span */
sealed interface GridCell {
val row: Int
val span: GridItemSpan
- val s: String
}
/**
@@ -40,7 +40,6 @@ data class TileGridCell(
override val row: Int,
override val width: Int,
override val span: GridItemSpan = GridItemSpan(width),
- override val s: String = "${tile.tileSpec.spec}-$row-$width",
val column: Int,
) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
val key: String = "${tile.tileSpec.spec}-$row"
@@ -52,12 +51,23 @@ data class TileGridCell(
) : this(tile = sizedTile.tile, row = row, column = column, width = sizedTile.width)
}
+/**
+ * Represents a [EditTileViewModel] from the edit mode available tiles grid and whether it is
+ * available to add or not.
+ */
+@Immutable
+data class AvailableTileGridCell(
+ override val tile: EditTileViewModel,
+ override val width: Int = 1,
+ val isAvailable: Boolean = true,
+ val key: TileSpec = tile.tileSpec,
+) : SizedTile<EditTileViewModel>, CategoryAndName by tile
+
/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
@Immutable
data class SpacerGridCell(
override val row: Int,
override val span: GridItemSpan = GridItemSpan(1),
- override val s: String = "spacer",
) : GridCell
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
index 03f0297e0d54..3287443f0405 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import com.android.systemui.dagger.SysUISingleton
@@ -25,6 +26,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@SysUISingleton
+@Stable
class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 19e542e6a21e..15e71c88bea1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -22,12 +22,18 @@ import android.service.quicksettings.Tile
import android.text.TextUtils
import android.widget.Switch
import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.tileimpl.SubtitleArrayMapping
import com.android.systemui.res.R
+import java.util.function.Supplier
+/**
+ * Ui State for the tiles. It doesn't contain the icon to be able to invalidate the icon part
+ * separately. For the icon, use [IconProvider].
+ */
@Immutable
data class TileUiState(
val label: String,
@@ -35,7 +41,6 @@ data class TileUiState(
val state: Int,
val handlesLongClick: Boolean,
val handlesSecondaryClick: Boolean,
- val icon: QSTile.Icon?,
val sideDrawable: Drawable?,
val accessibilityUiState: AccessibilityUiState,
)
@@ -90,7 +95,6 @@ fun QSTile.State.toUiState(resources: Resources): TileUiState {
state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state,
handlesLongClick = handlesLongClick,
handlesSecondaryClick = handlesSecondaryClick,
- icon = icon ?: iconSupplier?.get(),
sideDrawable = sideViewCustomDrawable,
AccessibilityUiState(
contentDescription?.toString() ?: "",
@@ -104,6 +108,14 @@ fun QSTile.State.toUiState(resources: Resources): TileUiState {
)
}
+fun QSTile.State.toIconProvider(): IconProvider {
+ return when {
+ icon != null -> IconProvider.ConstantIcon(icon)
+ iconSupplier != null -> IconProvider.IconSupplier(iconSupplier)
+ else -> IconProvider.Empty
+ }
+}
+
private fun QSTile.State.getStateText(resources: Resources): CharSequence {
val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
val array = resources.getStringArray(arrayResId)
@@ -114,3 +126,21 @@ private fun getUnavailableText(spec: String?, resources: Resources): String {
val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE]
}
+
+@Stable
+sealed interface IconProvider {
+
+ val icon: QSTile.Icon?
+
+ data class ConstantIcon(override val icon: QSTile.Icon) : IconProvider
+
+ data class IconSupplier(val supplier: Supplier<QSTile.Icon?>) : IconProvider {
+ override val icon: QSTile.Icon?
+ get() = supplier.get()
+ }
+
+ data object Empty : IconProvider {
+ override val icon: QSTile.Icon?
+ get() = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt
index e54bfa29d2db..10d7871b8ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import com.android.systemui.animation.Expandable
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap
+import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -46,6 +47,7 @@ class ToolbarViewModel
@AssistedInject
constructor(
editModeButtonViewModelFactory: EditModeButtonViewModel.Factory,
+ val buildNumberViewModelFactory: BuildNumberViewModel.Factory,
private val footerActionsInteractor: FooterActionsInteractor,
private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
private val falsingInteractor: FalsingInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
index 1cd5d100ec00..e3a8ffd0f480 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
@@ -6,7 +6,7 @@ import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.PackageChangeModel.Empty.user
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
index 88a49ee109aa..53d2554f0e82 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
index 76bfad936116..a3b4c71c7641 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
index e9c91ca0db12..66af6d8b3e18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
@@ -19,7 +19,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.Context
import android.hardware.display.ColorDisplayManager
import android.hardware.display.NightDisplayListener
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.NightDisplayListenerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
index 88d7f06dfada..ff3fd3781181 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
@@ -21,7 +21,7 @@ import android.content.pm.PackageManager
import android.content.res.Resources
import android.text.TextUtils
import com.android.systemui.res.R
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
index 3f619c08261d..c66c9dc9ba6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -18,7 +18,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.pm.UserInfo
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 5dc8d1bd4643..26148deb735e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -14,7 +14,7 @@ class QSPipelineFlagsRepository @Inject constructor() {
companion object Utils {
fun assertNewTiles() =
- RefactorFlagUtils.assertInNewMode(
+ RefactorFlagUtils.unsafeAssertInNewMode(
AconfigFlags.qsNewTiles(),
AconfigFlags.FLAG_QS_NEW_TILES
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index c6fc868b3dc8..0db05c1355ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -92,6 +92,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();
+ protected static final Object ARG_SHOW_TRANSIENT_DISABLING = new Object();
private static final int READY_STATE_NOT_READY = 0;
private static final int READY_STATE_READYING = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index 61a8fa3d2a6e..cd0b70e5e988 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -27,6 +27,7 @@ object SubtitleArrayMapping {
subtitleIdsMap["cell"] = R.array.tile_states_cell
subtitleIdsMap["battery"] = R.array.tile_states_battery
subtitleIdsMap["dnd"] = R.array.tile_states_dnd
+ subtitleIdsMap["modes_dnd"] = R.array.tile_states_modes_dnd
subtitleIdsMap["flashlight"] = R.array.tile_states_flashlight
subtitleIdsMap["rotation"] = R.array.tile_states_rotation
subtitleIdsMap["bt"] = R.array.tile_states_bt
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 4b1c90f0c2ce..9aeaa22c549e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -56,6 +56,7 @@ import com.android.systemui.plugins.qs.TileDetailsViewModel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -196,11 +197,18 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH);
final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
- final boolean enabled = transientEnabling || mController.isBluetoothEnabled();
+ final boolean transientDisabling =
+ QsInCompose.isEnabled() && arg == ARG_SHOW_TRANSIENT_DISABLING;
+ final boolean enabled =
+ transientEnabling || (mController.isBluetoothEnabled() && !transientDisabling);
final boolean connected = mController.isBluetoothConnected();
final boolean connecting = mController.isBluetoothConnecting();
- state.isTransient = transientEnabling || connecting ||
- mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
+ state.isTransient = transientEnabling || transientDisabling || connecting
+ || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
+ if (QsInCompose.isEnabled()) {
+ state.isTransient = state.isTransient
+ || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF;
+ }
if (!enabled || !connected || state.isTransient) {
stopListeningToStaleDeviceMetadata();
}
@@ -208,7 +216,8 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
state.value = enabled;
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.secondaryLabel = TextUtils.emptyIfNull(
- getSecondaryLabel(enabled, connecting, connected, state.isTransient));
+ getSecondaryLabel(enabled, connecting, connected,
+ state.isTransient && transientEnabling));
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth);
state.stateDescription = "";
@@ -241,8 +250,13 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
private void toggleBluetooth() {
final boolean isEnabled = mState.value;
- // Immediately enter transient enabling state when turning bluetooth on.
- refreshState(isEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING);
+ if (QsInCompose.isEnabled()) {
+ // Immediately enter transient enabling state when toggling bluetooth state.
+ refreshState(isEnabled ? ARG_SHOW_TRANSIENT_DISABLING : ARG_SHOW_TRANSIENT_ENABLING);
+ } else {
+ // Immediately enter transient enabling state when turning bluetooth on.
+ refreshState(isEnabled ? ARG_SHOW_TRANSIENT_DISABLING : null);
+ }
mController.setBluetoothEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt
new file mode 100644
index 000000000000..52b02066c35a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import androidx.annotation.DrawableRes
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.coroutineScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.animation.Expandable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.modes.shared.ModesUi
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.asQSTileIcon
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.runBlocking
+
+/**
+ * Standalone tile used to control the DND Mode. Contrast to [ModesTile] (the tile that opens a
+ * dialog showing the list of all modes) and [DndTile] (the tile used to toggle interruption
+ * filtering in the pre-MODES_UI world).
+ */
+class ModesDndTile
+@Inject
+constructor(
+ host: QSHost,
+ uiEventLogger: QsEventLogger,
+ @Background backgroundLooper: Looper,
+ @Main mainHandler: Handler,
+ falsingManager: FalsingManager,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ qsTileConfigProvider: QSTileConfigProvider,
+ private val dataInteractor: ModesDndTileDataInteractor,
+ private val tileMapper: ModesDndTileMapper,
+ private val userActionInteractor: ModesDndTileUserActionInteractor,
+) :
+ QSTileImpl<BooleanState>(
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ ) {
+
+ private lateinit var tileState: QSTileState
+ private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
+
+ init {
+ lifecycle.coroutineScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ dataInteractor.tileData().collect { refreshState(it) }
+ }
+ }
+ }
+
+ override fun isAvailable(): Boolean = ModesUi.isEnabled && android.app.Flags.modesUiDndTile()
+
+ override fun getTileLabel(): CharSequence =
+ mContext.getString(R.string.quick_settings_dnd_label)
+
+ override fun newTileState(): BooleanState = BooleanState()
+
+ override fun handleClick(expandable: Expandable?) = runBlocking {
+ userActionInteractor.handleClick()
+ }
+
+ override fun getLongClickIntent(): Intent? = userActionInteractor.getSettingsIntent()
+
+ @VisibleForTesting
+ public override fun handleUpdateState(state: BooleanState?, arg: Any?) {
+ val model = arg as? ModesDndTileModel ?: dataInteractor.getCurrentTileModel()
+
+ tileState = tileMapper.map(config, model)
+ state?.apply {
+ value = model.isActivated
+ this.state = tileState.activationState.legacyState
+ icon =
+ tileState.icon?.asQSTileIcon()
+ ?: maybeLoadResourceIcon(iconResId(model.isActivated))
+ label = tileLabel
+ secondaryLabel = tileState.secondaryLabel
+ contentDescription = tileState.contentDescription
+ expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
+ }
+ }
+
+ @DrawableRes
+ private fun iconResId(activated: Boolean): Int =
+ if (activated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
+
+ companion object {
+ const val TILE_SPEC = "modes_dnd"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt
index 7d396c58630e..8ffba1e5f3dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt
@@ -19,26 +19,14 @@ package com.android.systemui.qs.tiles.dialog
import android.view.LayoutInflater
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.res.R
@Composable
fun InternetDetailsContent(viewModel: InternetDetailsViewModel) {
val coroutineScope = rememberCoroutineScope()
- val context = LocalContext.current
-
- val internetDetailsContentManager = remember {
- viewModel.contentManagerFactory.create(
- canConfigMobileData = viewModel.getCanConfigMobileData(),
- canConfigWifi = viewModel.getCanConfigWifi(),
- coroutineScope = coroutineScope,
- context = context,
- )
- }
AndroidView(
modifier = Modifier.fillMaxSize(),
@@ -46,11 +34,11 @@ fun InternetDetailsContent(viewModel: InternetDetailsViewModel) {
// Inflate with the existing dialog xml layout and bind it with the manager
val view =
LayoutInflater.from(context).inflate(R.layout.internet_connectivity_dialog, null)
- internetDetailsContentManager.bind(view)
+ viewModel.internetDetailsContentManager.bind(view, coroutineScope)
view
// TODO: b/377388104 - Polish the internet details view UI
},
- onRelease = { internetDetailsContentManager.unBind() },
+ onRelease = { viewModel.internetDetailsContentManager.unBind() },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
index 659488bdd0d3..d8e1755e6cca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
@@ -43,6 +43,9 @@ import android.widget.Switch
import android.widget.TextView
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
@@ -79,8 +82,6 @@ constructor(
private val internetDetailsContentController: InternetDetailsContentController,
@Assisted(CAN_CONFIG_MOBILE_DATA) private val canConfigMobileData: Boolean,
@Assisted(CAN_CONFIG_WIFI) private val canConfigWifi: Boolean,
- @Assisted private val coroutineScope: CoroutineScope,
- @Assisted private var context: Context,
private val uiEventLogger: UiEventLogger,
@Main private val handler: Handler,
@Background private val backgroundExecutor: Executor,
@@ -121,26 +122,29 @@ constructor(
private lateinit var shareWifiButton: Button
private lateinit var airplaneModeButton: Button
private var alertDialog: AlertDialog? = null
-
- private val canChangeWifiState =
- WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context)
+ private var canChangeWifiState = false
private var wifiNetworkHeight = 0
private var backgroundOn: Drawable? = null
private var backgroundOff: Drawable? = null
private var clickJob: Job? = null
private var defaultDataSubId = internetDetailsContentController.defaultDataSubscriptionId
- @VisibleForTesting
- internal var adapter = InternetAdapter(internetDetailsContentController, coroutineScope)
+ @VisibleForTesting internal lateinit var adapter: InternetAdapter
@VisibleForTesting internal var wifiEntriesCount: Int = 0
@VisibleForTesting internal var hasMoreWifiEntries: Boolean = false
+ private lateinit var context: Context
+ private lateinit var coroutineScope: CoroutineScope
+
+ var title by mutableStateOf("")
+ private set
+
+ var subTitle by mutableStateOf("")
+ private set
@AssistedFactory
interface Factory {
fun create(
@Assisted(CAN_CONFIG_MOBILE_DATA) canConfigMobileData: Boolean,
@Assisted(CAN_CONFIG_WIFI) canConfigWifi: Boolean,
- coroutineScope: CoroutineScope,
- context: Context,
): InternetDetailsContentManager
}
@@ -152,12 +156,16 @@ constructor(
*
* @param contentView The view to which the content manager should be bound.
*/
- fun bind(contentView: View) {
+ fun bind(contentView: View, coroutineScope: CoroutineScope) {
if (DEBUG) {
Log.d(TAG, "Bind InternetDetailsContentManager")
}
this.contentView = contentView
+ context = contentView.context
+ this.coroutineScope = coroutineScope
+ adapter = InternetAdapter(internetDetailsContentController, coroutineScope)
+ canChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context)
initializeLifecycle()
initializeViews()
@@ -323,11 +331,11 @@ constructor(
}
}
- fun getTitleText(): String {
+ private fun getTitleText(): String {
return internetDetailsContentController.getDialogTitleText().toString()
}
- fun getSubtitleText(): String {
+ private fun getSubtitleText(): String {
return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString()
}
@@ -336,6 +344,13 @@ constructor(
Log.d(TAG, "updateDetailsUI ")
}
+ if (!::context.isInitialized) {
+ return
+ }
+
+ title = getTitleText()
+ subTitle = getSubtitleText()
+
airplaneModeButton.visibility =
if (internetContent.isAirplaneModeEnabled) View.VISIBLE else View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
index 6709fd2bb508..fb63bea4fb9f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
@@ -28,38 +28,27 @@ class InternetDetailsViewModel
@AssistedInject
constructor(
private val accessPointController: AccessPointController,
- val contentManagerFactory: InternetDetailsContentManager.Factory,
+ private val contentManagerFactory: InternetDetailsContentManager.Factory,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
-) : TileDetailsViewModel() {
- override fun clickOnSettingsButton() {
- qsTileIntentUserActionHandler.handle(
- /* expandable= */ null,
- Intent(Settings.ACTION_WIFI_SETTINGS),
+) : TileDetailsViewModel {
+ val internetDetailsContentManager by lazy {
+ contentManagerFactory.create(
+ canConfigMobileData = accessPointController.canConfigMobileData(),
+ canConfigWifi = accessPointController.canConfigWifi(),
)
}
- override fun getTitle(): String {
- // TODO: b/377388104 make title and sub title mutable states of string
- // by internetDetailsContentManager.getTitleText()
- // TODO: test title change between airplane mode and not airplane mode
- // TODO: b/377388104 Update the placeholder text
- return "Internet"
- }
+ override val title: String
+ get() = internetDetailsContentManager.title
- override fun getSubTitle(): String {
- // TODO: b/377388104 make title and sub title mutable states of string
- // by internetDetailsContentManager.getSubtitleText()
- // TODO: test subtitle change between airplane mode and not airplane mode
- // TODO: b/377388104 Update the placeholder text
- return "Tab a network to connect"
- }
+ override val subTitle: String
+ get() = internetDetailsContentManager.subTitle
- fun getCanConfigMobileData(): Boolean {
- return accessPointController.canConfigMobileData()
- }
-
- fun getCanConfigWifi(): Boolean {
- return accessPointController.canConfigWifi()
+ override fun clickOnSettingsButton() {
+ qsTileIntentUserActionHandler.handle(
+ /* expandable= */ null,
+ Intent(Settings.ACTION_WIFI_SETTINGS),
+ )
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
index 9a39c3c095ef..4f7e03bd3bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
@@ -23,18 +23,14 @@ import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogView
class ModesDetailsViewModel(
private val onSettingsClick: () -> Unit,
val viewModel: ModesDialogViewModel,
-) : TileDetailsViewModel() {
+) : TileDetailsViewModel {
override fun clickOnSettingsButton() {
onSettingsClick()
}
- override fun getTitle(): String {
- // TODO(b/388321032): Replace this string with a string in a translatable xml file.
- return "Modes"
- }
+ // TODO(b/388321032): Replace this string with a string in a translatable xml file.
+ override val title = "Modes"
- override fun getSubTitle(): String {
- // TODO(b/388321032): Replace this string with a string in a translatable xml file.
- return "Silences interruptions from people and apps in different circumstances"
- }
+ // TODO(b/388321032): Replace this string with a string in a translatable xml file.
+ override val subTitle = "Silences interruptions from people and apps in different circumstances"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
index c84ddb6fdb36..59f209edb546 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
@@ -23,19 +23,15 @@ import com.android.systemui.screenrecord.RecordingController
class ScreenRecordDetailsViewModel(
val recordingController: RecordingController,
val onStartRecordingClicked: Runnable,
-) : TileDetailsViewModel() {
+) : TileDetailsViewModel {
override fun clickOnSettingsButton() {
// No settings button in this tile.
}
- override fun getTitle(): String {
- // TODO(b/388321032): Replace this string with a string in a translatable xml file,
- return "Screen recording"
- }
+ // TODO(b/388321032): Replace this string with a string in a translatable xml file,
+ override val title = "Screen recording"
- override fun getSubTitle(): String {
- // No sub-title in this tile.
- return ""
- }
+ // No sub-title in this tile.
+ override val subTitle = ""
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
index 1544804c3291..38eb5947bd71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt
index 5e7172ee3ba7..268efcef9062 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt
@@ -21,6 +21,7 @@ import android.provider.Settings
import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager
import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.shared.QSSettingsPackageRepository
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
@@ -37,6 +38,7 @@ constructor(
@Main private val mainContext: CoroutineContext,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
private val hearingDevicesDialogManager: HearingDevicesDialogManager,
+ private val settingsPackageRepository: QSSettingsPackageRepository,
) : QSTileUserActionInteractor<HearingDevicesTileModel> {
override suspend fun handleInput(input: QSTileInput<HearingDevicesTileModel>) =
@@ -53,7 +55,8 @@ constructor(
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
action.expandable,
- Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS),
+ Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS)
+ .setPackage(settingsPackageRepository.getSettingsPackageName()),
)
}
is QSTileUserAction.ToggleClick -> {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt
new file mode 100644
index 000000000000..b1ae3ba4381a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.app.tracing.coroutines.flow.flowName
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+class ModesDndTileDataInteractor
+@Inject
+constructor(
+ @ShadeDisplayAware val context: Context,
+ val zenModeInteractor: ZenModeInteractor,
+ @Background val bgDispatcher: CoroutineDispatcher,
+) : QSTileDataInteractor<ModesDndTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>,
+ ): Flow<ModesDndTileModel> = tileData()
+
+ /**
+ * An adapted version of the base class' [tileData] method for use in an old-style tile.
+ *
+ * TODO(b/299909989): Remove after the transition.
+ */
+ fun tileData() =
+ zenModeInteractor.dndMode
+ .filterNotNull()
+ .map { dndMode -> buildTileData(dndMode) }
+ .flowName("tileData")
+ .flowOn(bgDispatcher)
+ .distinctUntilChanged()
+
+ fun getCurrentTileModel() = buildTileData(zenModeInteractor.getDndMode())
+
+ private fun buildTileData(dndMode: ZenMode): ModesDndTileModel {
+ return ModesDndTileModel(isActivated = dndMode.isActive)
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(ModesUi.isEnabled && android.app.Flags.modesUiDndTile())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt
new file mode 100644
index 000000000000..e8fcea070ede
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
+import android.util.Log
+import com.android.systemui.animation.Expandable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.shared.QSSettingsPackageRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class ModesDndTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val mainContext: CoroutineContext,
+ private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
+ // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
+ private val dialogDelegate: ModesDialogDelegate,
+ private val zenModeInteractor: ZenModeInteractor,
+ private val dialogEventLogger: ModesDialogEventLogger,
+ private val settingsPackageRepository: QSSettingsPackageRepository,
+) : QSTileUserActionInteractor<ModesDndTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<ModesDndTileModel>) {
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click,
+ is QSTileUserAction.ToggleClick -> {
+ handleClick()
+ }
+ is QSTileUserAction.LongClick -> {
+ handleLongClick(action.expandable)
+ }
+ }
+ }
+ }
+
+ suspend fun handleClick() {
+ val dnd = zenModeInteractor.dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "No DND!?")
+ return
+ }
+
+ if (!dnd.isActive) {
+ if (zenModeInteractor.shouldAskForZenDuration(dnd)) {
+ dialogEventLogger.logOpenDurationDialog(dnd)
+ withContext(mainContext) {
+ // NOTE: The dialog handles turning on the mode itself.
+ val dialog = dialogDelegate.makeDndDurationDialog()
+ dialog.show()
+ }
+ } else {
+ dialogEventLogger.logModeOn(dnd)
+ zenModeInteractor.activateMode(dnd)
+ }
+ } else {
+ dialogEventLogger.logModeOff(dnd)
+ zenModeInteractor.deactivateMode(dnd)
+ }
+ }
+
+ private fun handleLongClick(expandable: Expandable?) {
+ val intent = getSettingsIntent()
+ if (intent != null) {
+ qsTileIntentUserInputHandler.handle(expandable, intent)
+ }
+ }
+
+ fun getSettingsIntent(): Intent? {
+ val dnd = zenModeInteractor.dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "No DND!?")
+ return null
+ }
+
+ return Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, dnd.id)
+ .setPackage(settingsPackageRepository.getSettingsPackageName())
+ }
+
+ companion object {
+ const val TAG = "ModesDndTileUserActionInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt
new file mode 100644
index 000000000000..eab798897aa3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesDndTileModel.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.model
+
+data class ModesDndTileModel(val isActivated: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt
new file mode 100644
index 000000000000..4869b6f74554
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.ui
+
+import android.content.res.Resources
+import android.widget.Switch
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
+import javax.inject.Inject
+
+class ModesDndTileMapper
+@Inject
+constructor(@ShadeDisplayAware private val resources: Resources, val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ModesDndTileModel> {
+ override fun map(config: QSTileConfig, data: ModesDndTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ val iconResource =
+ if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
+ icon =
+ Icon.Loaded(
+ resources.getDrawable(iconResource, theme),
+ res = iconResource,
+ contentDescription = null,
+ )
+
+ activationState =
+ if (data.isActivated) {
+ QSTileState.ActivationState.ACTIVE
+ } else {
+ QSTileState.ActivationState.INACTIVE
+ }
+ label = resources.getString(R.string.quick_settings_dnd_label)
+ secondaryLabel =
+ resources.getString(
+ if (data.isActivated) R.string.zen_mode_on else R.string.zen_mode_off
+ )
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ expandedAccessibilityClass = Switch::class
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
index 7117629622e6..a8e9c5663f39 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
@@ -22,7 +22,7 @@ import android.hardware.SensorPrivacyManager.Sensors.Sensor
import android.os.UserHandle
import android.provider.DeviceConfig
import android.util.Log
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
index bc15bbb5e57d..263ef09ea767 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.feature.flags.Flags
import androidx.annotation.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -28,8 +30,11 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/**
* Provides a {@link com.android.systemui.statusbar.phone.SystemUIDialog} to be shown on the inner
@@ -46,6 +51,7 @@ internal constructor(
private val rearDisplayStateInteractor: RearDisplayStateInteractor,
private val rearDisplayInnerDialogDelegateFactory: RearDisplayInnerDialogDelegate.Factory,
@Application private val scope: CoroutineScope,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : CoreStartable, AutoCloseable {
companion object {
@@ -53,6 +59,16 @@ internal constructor(
}
@VisibleForTesting var stateChangeListener: Job? = null
+ private val keyguardVisible = MutableStateFlow(false)
+ private val keyguardVisibleFlow = keyguardVisible.asStateFlow()
+
+ @VisibleForTesting
+ val keyguardCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ keyguardVisible.value = visible
+ }
+ }
override fun close() {
stateChangeListener?.cancel()
@@ -62,28 +78,39 @@ internal constructor(
if (Flags.deviceStateRdmV2()) {
var dialog: SystemUIDialog? = null
+ keyguardUpdateMonitor.registerCallback(keyguardCallback)
+
stateChangeListener =
- rearDisplayStateInteractor.state
- .map {
- when (it) {
- is RearDisplayStateInteractor.State.Enabled -> {
- val rearDisplayContext =
- context.createDisplayContext(it.innerDisplay)
- val delegate =
- rearDisplayInnerDialogDelegateFactory.create(
- rearDisplayContext,
- deviceStateManager::cancelStateRequest,
- )
- dialog = delegate.createDialog().apply { show() }
- }
+ scope.launch {
+ combine(rearDisplayStateInteractor.state, keyguardVisibleFlow) {
+ rearDisplayState,
+ keyguardVisible ->
+ Pair(rearDisplayState, keyguardVisible)
+ }
+ .collectLatest { (rearDisplayState, keyguardVisible) ->
+ when (rearDisplayState) {
+ is RearDisplayStateInteractor.State.Enabled -> {
+ if (!keyguardVisible) {
+ val rearDisplayContext =
+ context.createDisplayContext(
+ rearDisplayState.innerDisplay
+ )
+ val delegate =
+ rearDisplayInnerDialogDelegateFactory.create(
+ rearDisplayContext,
+ deviceStateManager::cancelStateRequest,
+ )
+ dialog = delegate.createDialog().apply { show() }
+ }
+ }
- is RearDisplayStateInteractor.State.Disabled -> {
- dialog?.dismiss()
- dialog = null
+ is RearDisplayStateInteractor.State.Disabled -> {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
- }
- .launchIn(scope)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index 9af4630bf492..4be35f147c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -66,6 +66,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -94,6 +95,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.model.SysUiState.SysUiStateCallback;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.views.NavigationBar;
@@ -584,7 +586,8 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
// Force-update the systemui state flags
updateSystemUiStateFlags();
- notifySystemUiStateFlags(mSysUiState.getFlags());
+ // TODO b/398011576 - send the state for all displays.
+ notifySystemUiStateFlags(mSysUiState.getFlags(), Display.DEFAULT_DISPLAY);
notifyConnectionChanged();
}
@@ -650,6 +653,13 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
}
};
+ private final SysUiStateCallback mSysUiStateCallback =
+ new SysUiStateCallback() {
+ @Override
+ public void onSystemUiStateChanged(long sysUiFlags, int displayId) {
+ notifySystemUiStateFlags(sysUiFlags, displayId);
+ }
+ };
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public LauncherProxyService(Context context,
@@ -708,8 +718,10 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
com.android.internal.R.string.config_recentsComponentName));
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
+ // TODO b/398011576 - Here we're still only handling the default display state. We should
+ // have a callback for any sysuiState change.
mSysUiState = sysUiState;
- mSysUiState.addCallback(this::notifySystemUiStateFlags);
+ mSysUiState.addCallback(mSysUiStateCallback);
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
@@ -815,14 +827,15 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
}
}
- private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
+ private void notifySystemUiStateFlags(@SystemUiStateFlags long flags, int displayId) {
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Notifying sysui state change to launcher service: proxy="
- + mLauncherProxy + " flags=" + flags);
+ + mLauncherProxy + " display=" + displayId + " flags="
+ + QuickStepContract.getSystemUiStateString(flags) + " displayId=" + displayId);
}
try {
if (mLauncherProxy != null) {
- mLauncherProxy.onSystemUiStateChanged(flags);
+ mLauncherProxy.onSystemUiStateChanged(flags, displayId);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify sysui state change", e);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt
index 4044381e25e7..ecd93b2a4946 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt
@@ -69,7 +69,7 @@ constructor(
}
fun addCallback(callback: IKeyguardStateCallback) {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
callbacks.add(callback)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 4753b9ac0457..3ad0867192d3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -33,7 +33,7 @@ import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
@@ -687,7 +687,7 @@ constructor(
if (!isDeviceEntered) {
coroutineScope {
launch {
- deviceEntryHapticsInteractor.playSuccessHaptic
+ deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry
.sample(sceneInteractor.currentScene)
.collect { currentScene ->
if (Flags.msdlFeedback()) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 733b4210950f..634323c1fe2e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -83,7 +83,9 @@ object SceneContainerFlag {
* testing.
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, DESCRIPTION)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, DESCRIPTION)
/** Returns a developer-readable string that describes the current requirement list. */
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 0dd0e3dd87c6..1d470c2143da 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -33,6 +33,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
sceneDataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
sceneJankMonitorFactory: SceneJankMonitor.Factory,
+ windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler,
) {
setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
@@ -53,6 +54,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
qsSceneAdapter = qsSceneAdapter,
sceneJankMonitorFactory = sceneJankMonitorFactory,
)
+ setWindowRootViewKeyEventHandler(windowRootViewKeyEventHandler)
}
override fun setVisibility(visibility: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 364da5f8e80d..42bf75337270 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.util.AttributeSet
import android.util.Pair
import android.view.DisplayCutout
+import android.view.KeyEvent
import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
@@ -37,6 +38,11 @@ open class WindowRootView(context: Context, attrs: AttributeSet?) : FrameLayout(
private var rightInset = 0
private var previousInsets: WindowInsets? = null
+ private lateinit var windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler
+
+ fun setWindowRootViewKeyEventHandler(wrvkeh: WindowRootViewKeyEventHandler) {
+ windowRootViewKeyEventHandler = wrvkeh
+ }
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -137,6 +143,24 @@ open class WindowRootView(context: Context, attrs: AttributeSet?) : FrameLayout(
return parent.let { it !is View || it.id == android.R.id.content }
}
+ override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ windowRootViewKeyEventHandler.collectKeyEvent(event)
+
+ if (windowRootViewKeyEventHandler.interceptMediaKey(event)) {
+ return true
+ }
+
+ if (super.dispatchKeyEvent(event)) {
+ return true
+ }
+
+ return windowRootViewKeyEventHandler.dispatchKeyEvent(event)
+ }
+
+ override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ return windowRootViewKeyEventHandler.dispatchKeyEventPreIme(event) ?: false
+ }
+
/** Controller responsible for calculating insets for the shade window. */
interface LayoutInsetsController {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt
new file mode 100644
index 000000000000..863af92a3b49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scene.ui.view
+
+import android.view.KeyEvent
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
+import dagger.Lazy
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowRootViewKeyEventHandler
+@Inject
+constructor(
+ val sysUIKeyEventHandlerLazy: Lazy<SysUIKeyEventHandler>,
+ val falsingCollector: FalsingCollector,
+) {
+ fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ return sysUIKeyEventHandlerLazy.get().dispatchKeyEvent(event)
+ }
+
+ fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ return sysUIKeyEventHandlerLazy.get().dispatchKeyEventPreIme(event)
+ }
+
+ fun interceptMediaKey(event: KeyEvent): Boolean {
+ return sysUIKeyEventHandlerLazy.get().interceptMediaKey(event)
+ }
+
+ /** Collects the KeyEvent without intercepting it. */
+ fun collectKeyEvent(event: KeyEvent) {
+ falsingCollector.onKeyEvent(event)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 9208fc3dd016..271f4dce0aab 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,18 +23,31 @@ import android.content.ContentProvider
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.screenshot.scroll.LongScreenshotActivity
import com.android.systemui.shared.Flags.usePreferredImageEditor
+import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
@SysUISingleton
class ActionIntentCreator
@Inject
-constructor(private val context: Context, private val packageManager: PackageManager) {
+constructor(
+ private val context: Context,
+ private val packageManager: PackageManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
/** @return a chooser intent to share the given URI. */
fun createShare(uri: Uri): Intent = createShare(uri, subject = null, text = null)
@@ -76,11 +89,16 @@ constructor(private val context: Context, private val packageManager: PackageMan
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
+ // Non-suspend version for java compat
+ fun createEdit(rawUri: Uri, consumer: Consumer<Intent>) {
+ applicationScope.launch { consumer.accept(createEdit(rawUri)) }
+ }
+
/**
* @return an ACTION_EDIT intent for the given URI, directed to config_preferredScreenshotEditor
* if enabled, falling back to config_screenshotEditor if that's non-empty.
*/
- fun createEdit(rawUri: Uri): Intent {
+ suspend fun createEdit(rawUri: Uri): Intent {
val uri = uriWithoutUserId(rawUri)
val editIntent = Intent(Intent.ACTION_EDIT)
@@ -112,22 +130,30 @@ constructor(private val context: Context, private val packageManager: PackageMan
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
}
- private fun preferredEditor(): ComponentName? =
+ private suspend fun preferredEditor(): ComponentName? =
runCatching {
val preferredEditor = context.getString(R.string.config_preferredScreenshotEditor)
val component = ComponentName.unflattenFromString(preferredEditor) ?: return null
+ return if (isComponentAvailable(component)) component else null
+ }
+ .getOrNull()
+
+ private suspend fun isComponentAvailable(component: ComponentName): Boolean =
+ withContext(backgroundDispatcher) {
+ try {
val info =
packageManager.getPackageInfo(
component.packageName,
PackageManager.GET_ACTIVITIES,
)
-
- return info.activities
- ?.firstOrNull { it.componentName.className.equals(component.className) }
- ?.componentName
+ info.activities?.firstOrNull {
+ it.componentName.className == component.className
+ } != null
+ } catch (e: NameNotFoundException) {
+ false
}
- .getOrNull()
+ }
private fun defaultEditor(): ComponentName? =
runCatching {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 4373389f4a51..d91f267ff3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -22,6 +22,7 @@ import android.net.Uri
import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
@@ -34,6 +35,8 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
@@ -68,6 +71,7 @@ constructor(
private val context: Context,
private val uiEventLogger: UiEventLogger,
private val actionIntentCreator: ActionIntentCreator,
+ @Application private val applicationScope: CoroutineScope,
@Assisted val requestId: UUID,
@Assisted val request: ScreenshotData,
@Assisted val actionExecutor: ActionExecutor,
@@ -75,7 +79,7 @@ constructor(
) : ScreenshotActionsProvider {
private var addedScrollChip = false
private var onScrollClick: Runnable? = null
- private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+ private var pendingAction: (suspend (ScreenshotSavedResult) -> Unit)? = null
private var result: ScreenshotSavedResult? = null
private var webUri: Uri? = null
@@ -166,15 +170,16 @@ constructor(
return
}
this.result = result
- pendingAction?.invoke(result)
+ pendingAction?.also { applicationScope.launch { it.invoke(result) } }
}
override fun onAssistContent(assistContent: AssistContent?) {
webUri = assistContent?.webUri
}
- private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
- result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
+ private fun onDeferrableActionTapped(onResult: suspend (ScreenshotSavedResult) -> Unit) {
+ result?.let { applicationScope.launch { onResult.invoke(it) } }
+ ?: run { pendingAction = onResult }
}
@AssistedFactory
@@ -188,6 +193,6 @@ constructor(
}
companion object {
- private const val TAG = "ScreenshotActionsProvider"
+ private const val TAG = "ScreenshotActionsPrvdr"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index ecea30f1b1c3..88ffd4fa2750 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -352,30 +352,35 @@ public class LongScreenshotActivity extends Activity {
private void doEdit(Uri uri) {
if (mScreenshotUserHandle != Process.myUserHandle()) {
// TODO: Fix transition for work profile. Omitting it in the meantime.
- mActionExecutor.launchIntentAsync(
- mActionIntentCreator.createEdit(uri),
- mScreenshotUserHandle, false,
- /* activityOptions */ null, /* transitionCoordinator */ null);
+ mActionIntentCreator.createEdit(uri, intent -> {
+ mActionExecutor.launchIntentAsync(
+ intent,
+ mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
+ });
+
} else {
if (usePreferredImageEditor()) {
- Intent intent = mActionIntentCreator.createEdit(uri);
- Bundle options = null;
-
- if (intent.getComponent() != null) {
- // Modify intent for shared transition if we're opening a specific editor.
- intent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.removeFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mTransitionView.setImageBitmap(mOutputBitmap);
- mTransitionView.setVisibility(View.VISIBLE);
- mTransitionView.setTransitionName(
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
- options = ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle();
- // TODO: listen for transition completing instead of finishing onStop
- mTransitionStarted = true;
- }
+ mActionIntentCreator.createEdit(uri, intent -> {
+ Bundle options = null;
+
+ if (intent.getComponent() != null) {
+ // Modify intent for shared transition if we're opening a specific editor.
+ intent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.removeFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
+ mTransitionView.setTransitionName(
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+ options = ActivityOptions.makeSceneTransitionAnimation(this,
+ mTransitionView,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle();
+ // TODO: listen for transition completing instead of finishing onStop
+ mTransitionStarted = true;
+ }
- startActivity(intent, options);
+ startActivity(intent, options);
+ });
} else {
String editorPackage = getString(R.string.config_screenshotEditor);
Intent intent = new Intent(Intent.ACTION_EDIT);
diff --git a/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt
index 7e967f436ecb..0b039fecc19e 100644
--- a/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.security.data.repository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.security.data.model.SecurityModel
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 eae0ba66925d..ade62a9b63d3 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -164,16 +164,17 @@ public class BrightnessDialog extends Activity {
container.setVisibility(View.VISIBLE);
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) container.getLayoutParams();
- int horizontalMargin =
- getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
- lp.leftMargin = horizontalMargin;
- lp.rightMargin = horizontalMargin;
-
- int verticalMargin =
- getResources().getDimensionPixelSize(
- R.dimen.notification_guts_option_vertical_padding);
-
- lp.topMargin = verticalMargin;
+ // Remove the margin. Have the container take all the space. Instead, insert padding.
+ // This allows for the background to be visible around the slider.
+ int margin = 0;
+ lp.topMargin = margin;
+ lp.bottomMargin = margin;
+ lp.leftMargin = margin;
+ lp.rightMargin = margin;
+ int padding = getResources().getDimensionPixelSize(
+ R.dimen.rounded_slider_background_padding
+ );
+ container.setPadding(padding, padding, padding, padding);
// If in multi-window or freeform, increase the top margin so the brightness dialog
// doesn't get cut off.
final int windowingMode = configuration.windowConfiguration.getWindowingMode();
@@ -182,17 +183,15 @@ public class BrightnessDialog extends Activity {
lp.topMargin += 50;
}
- lp.bottomMargin = verticalMargin;
-
int orientation = configuration.orientation;
int windowWidth = getWindowAvailableWidth();
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
boolean shouldBeFullWidth = getIntent()
.getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
- lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - horizontalMargin * 2;
+ lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - margin * 2;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- lp.width = windowWidth - horizontalMargin * 2;
+ lp.width = windowWidth - margin * 2;
}
container.setLayoutParams(lp);
@@ -202,7 +201,7 @@ public class BrightnessDialog extends Activity {
// 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);
+ bounds.set(-margin, 0, right - left + margin, bottom - top);
v.setSystemGestureExclusionRects(List.of(bounds));
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
index 9e20055de856..962a3bd2376b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
@@ -17,12 +17,15 @@
package com.android.systemui.settings.brightness
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.compose.ui.unit.dp
import com.android.compose.theme.PlatformTheme
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.brightness.ui.compose.ContainerColors
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.lifecycle.rememberViewModel
@@ -44,7 +47,11 @@ private fun BrightnessSliderForDialog(
rememberViewModel(traceName = "BrightnessDialog.viewModel") {
brightnessSliderViewModelFactory.create(false)
}
- BrightnessSliderContainer(viewModel = viewModel, Modifier.fillMaxWidth())
+ BrightnessSliderContainer(
+ viewModel = viewModel,
+ containerColors = ContainerColors.singleColor(ContainerColors.defaultContainerColor),
+ modifier = Modifier.fillMaxWidth().padding(8.dp),
+ )
}
class ComposableProvider(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3be2f1b7b957..362b5db012e1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.content.Context
+import android.content.res.Configuration
import android.graphics.Rect
import android.os.PowerManager
import android.os.SystemClock
@@ -25,11 +26,13 @@ import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
+import androidx.core.view.updateMargins
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleObserver
@@ -101,7 +104,10 @@ constructor(
) : LifecycleOwner {
private val logger = Logger(logBuffer, TAG)
- private class CommunalWrapper(context: Context) : FrameLayout(context) {
+ private class CommunalWrapper(
+ context: Context,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ ) : FrameLayout(context) {
private val consumers: MutableSet<Consumer<Boolean>> = ArraySet()
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
@@ -121,6 +127,24 @@ constructor(
consumers.clear()
}
}
+
+ override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
+ if (
+ !communalSettingsInteractor.isV2FlagEnabled() ||
+ resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
+ ) {
+ return super.onApplyWindowInsets(windowInsets)
+ }
+ val type = WindowInsets.Type.displayCutout()
+ val insets = windowInsets.getInsets(type)
+
+ // Reset horizontal margins added by window insets, so hub can be edge to edge.
+ if (insets.left > 0 || insets.right > 0) {
+ val lp = layoutParams as LayoutParams
+ lp.updateMargins(0, lp.topMargin, 0, lp.bottomMargin)
+ }
+ return WindowInsets.CONSUMED
+ }
}
/** The container view for the hub. This will not be initialized until [initView] is called. */
@@ -443,7 +467,8 @@ constructor(
collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
collectFlow(containerView, communalViewModel.swipeToHubEnabled, { swipeToHubEnabled = it })
- communalContainerWrapper = CommunalWrapper(containerView.context)
+ communalContainerWrapper =
+ CommunalWrapper(containerView.context, communalSettingsInteractor)
communalContainerWrapper?.addView(communalContainerView)
logger.d("Hub container initialized")
return communalContainerWrapper!!
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index eea04701a9a4..24e7976011f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -65,6 +65,7 @@ import android.os.Trace;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
+import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -96,6 +97,7 @@ import com.android.systemui.Gefingerpoken;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -119,6 +121,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.model.StateChange;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -137,6 +140,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.data.repository.FlingInfo;
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
@@ -201,6 +205,7 @@ import dalvik.annotation.optimization.NeverCompile;
import com.google.android.msdl.data.model.MSDLToken;
import com.google.android.msdl.domain.MSDLPlayer;
+import dagger.Lazy;
import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -391,7 +396,7 @@ public final class NotificationPanelViewController implements
/** Whether the notifications are displayed full width (no margins on the side). */
private boolean mIsFullWidth;
private boolean mBlockingExpansionForCurrentTouch;
- // Following variables maintain state of events when input focus transfer may occur.
+ // Following variables maintain state of events when input focus transfer may occur.
private boolean mExpectingSynthesizedDown;
private boolean mLastEventSynthesizedDown;
@@ -450,7 +455,10 @@ public final class NotificationPanelViewController implements
private final MediaDataManager mMediaDataManager;
@PanelState
private int mCurrentPanelState = STATE_CLOSED;
+ @Deprecated // Use SysUIStateInteractor instead
private final SysUiState mSysUiState;
+ private final SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
+ private final Lazy<ShadeDisplaysRepository> mShadeDisplaysRepository;
private final NotificationShadeDepthController mDepthController;
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
@@ -607,6 +615,7 @@ public final class NotificationPanelViewController implements
ShadeRepository shadeRepository,
Optional<SysUIUnfoldComponent> unfoldComponent,
SysUiState sysUiState,
+ SysUIStateDisplaysInteractor sysUIStateDisplaysInteractor,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
@@ -631,7 +640,8 @@ public final class NotificationPanelViewController implements
KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
MSDLPlayer msdlPlayer,
BrightnessMirrorShowingRepository brightnessMirrorShowingRepository,
- BlurConfig blurConfig) {
+ BlurConfig blurConfig,
+ Lazy<ShadeDisplaysRepository> shadeDisplaysRepository) {
mBlurConfig = blurConfig;
SceneContainerFlag.assertInLegacyMode();
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@@ -738,6 +748,8 @@ public final class NotificationPanelViewController implements
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
+ mSysUIStateDisplaysInteractor = sysUIStateDisplaysInteractor;
+ mShadeDisplaysRepository = shadeDisplaysRepository;
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -2253,7 +2265,7 @@ public final class NotificationPanelViewController implements
Log.i(TAG, "Ignoring status Bar long press on virtualized test device.");
return;
}
- ShadeExpandsOnStatusBarLongPress.assertInNewMode();
+ ShadeExpandsOnStatusBarLongPress.unsafeAssertInNewMode();
mStatusBarLongPressDowntime = event.getDownTime();
if (isTracking()) {
onTrackingStopped(true);
@@ -2701,13 +2713,50 @@ public final class NotificationPanelViewController implements
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
+ isFullyExpanded() + " inQs=" + mQsController.getExpanded());
}
+ if (ShadeWindowGoesAround.isEnabled()) {
+ setPerDisplaySysUIStateFlags();
+ } else {
+ setDefaultDisplayFlags();
+ }
+ }
+
+ private int getShadeDisplayId() {
+ if (ShadeWindowGoesAround.isEnabled()) {
+ var pendingDisplayId =
+ mShadeDisplaysRepository.get().getPendingDisplayId().getValue();
+ // Use the pendingDisplayId from the repository, *not* the Shade's context.
+ // This ensures correct UI state updates also if this method is called just *before*
+ // the Shade window moves to another display.
+ // The pendingDisplayId is guaranteed to be updated before this method is called.
+ return pendingDisplayId;
+ } else {
+ return Display.DEFAULT_DISPLAY;
+ }
+ }
+
+ private void setPerDisplaySysUIStateFlags() {
+ mSysUIStateDisplaysInteractor.setFlagsExclusivelyToDisplay(
+ getShadeDisplayId(),
+ new StateChange()
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ isPanelExpanded() && !isCollapsing())
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ isFullyExpanded() && !mQsController.getExpanded())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+ isFullyExpanded() && mQsController.getExpanded())
+ );
+ }
+
+ @Deprecated
+ private void setDefaultDisplayFlags() {
mSysUiState
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
isPanelExpanded() && !isCollapsing())
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
isFullyExpanded() && !mQsController.getExpanded())
.setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
- isFullyExpanded() && mQsController.getExpanded()).commitUpdate(mDisplayId);
+ isFullyExpanded() && mQsController.getExpanded()).commitUpdate(
+ mDisplayId);
}
private void debugLog(String fmt, Object... args) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 305444f7ab5e..8d7cc922c38b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -30,8 +30,6 @@ import android.graphics.Region;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.IWindow;
@@ -75,7 +73,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.util.settings.SecureSettings;
import dagger.Lazy;
@@ -134,7 +131,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private final SysuiColorExtractor mColorExtractor;
private final NotificationShadeWindowModel mNotificationShadeWindowModel;
- private final SecureSettings mSecureSettings;
/**
* Layout params would be aggregated and dispatched all at once if this is > 0.
*
@@ -168,7 +164,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
NotificationShadeWindowModel notificationShadeWindowModel,
- SecureSettings secureSettings,
Lazy<CommunalInteractor> communalInteractor,
@ShadeDisplayAware LayoutParams shadeWindowLayoutParams) {
mContext = context;
@@ -186,7 +181,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mBackgroundExecutor = backgroundExecutor;
mColorExtractor = colorExtractor;
mNotificationShadeWindowModel = notificationShadeWindowModel;
- mSecureSettings = secureSettings;
// prefix with {slow} to make sure this dumps at the END of the critical section.
dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
mAuthController = authController;
@@ -424,7 +418,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
(long) mLpChanged.preferredMaxDisplayRefreshRate);
}
- if (state.bouncerShowing && !isSecureWindowsDisabled()) {
+ if (state.bouncerShowing) {
mLpChanged.flags |= LayoutParams.FLAG_SECURE;
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -437,13 +431,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- private boolean isSecureWindowsDisabled() {
- return mSecureSettings.getIntForUser(
- Settings.Secure.DISABLE_SECURE_WINDOWS,
- 0,
- UserHandle.USER_CURRENT) == 1;
- }
-
private void adjustScreenOrientation(NotificationShadeWindowState state) {
if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) {
if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
@@ -511,17 +498,18 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
private boolean isExpanded(NotificationShadeWindowState state) {
+ boolean visForBlur = !Flags.disableBlurredShadeVisible() && state.backgroundBlurRadius > 0;
boolean isExpanded = !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
|| state.headsUpNotificationShowing
|| state.scrimsVisibility != ScrimController.TRANSPARENT)
- || state.backgroundBlurRadius > 0
+ || visForBlur
|| state.launchingActivityFromNotification;
mLogger.logIsExpanded(isExpanded, state.forceWindowCollapsed,
state.isKeyguardShowingAndNotOccluded(), state.panelVisible,
state.keyguardFadingAway, state.bouncerShowing, state.headsUpNotificationShowing,
state.scrimsVisibility != ScrimController.TRANSPARENT,
- state.backgroundBlurRadius > 0, state.launchingActivityFromNotification);
+ visForBlur, state.launchingActivityFromNotification);
return isExpanded;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 1721700d2aaf..84efdc274521 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -105,26 +105,6 @@ public class NotificationShadeWindowView extends WindowRootView {
}
}
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- mInteractionEventHandler.collectKeyEvent(event);
-
- if (mInteractionEventHandler.interceptMediaKey(event)) {
- return true;
- }
-
- if (super.dispatchKeyEvent(event)) {
- return true;
- }
-
- return mInteractionEventHandler.dispatchKeyEvent(event);
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- return mInteractionEventHandler.dispatchKeyEventPreIme(event);
- }
-
protected void setInteractionEventHandler(InteractionEventHandler listener) {
mInteractionEventHandler = listener;
}
@@ -363,17 +343,6 @@ public class NotificationShadeWindowView extends WindowRootView {
boolean handleTouchEvent(MotionEvent ev);
void didNotHandleTouchEvent(MotionEvent ev);
-
- boolean interceptMediaKey(KeyEvent event);
-
- boolean dispatchKeyEvent(KeyEvent event);
-
- boolean dispatchKeyEventPreIme(KeyEvent event);
-
- /**
- * Collects the KeyEvent without intercepting it
- */
- void collectKeyEvent(KeyEvent event);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 10a9fd20ee5a..c74f6c3fd914 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -27,7 +27,6 @@ import android.app.StatusBarManager;
import android.util.Log;
import android.view.Choreographer;
import android.view.GestureDetector;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -50,7 +49,6 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
-import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.Edge;
@@ -60,6 +58,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
@@ -89,7 +88,6 @@ import com.android.systemui.window.ui.WindowRootViewBinder;
import com.android.systemui.window.ui.viewmodel.WindowRootViewModel;
import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.flow.Flow;
import java.io.PrintWriter;
@@ -117,8 +115,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
- private final FeatureFlagsClassic mFeatureFlagsClassic;
- private final SysUIKeyEventHandler mSysUIKeyEventHandler;
+ private final WindowRootViewKeyEventHandler mWindowRootViewKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final QuickSettingsController mQuickSettingsController;
@@ -202,7 +199,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
- SysUIKeyEventHandler sysUIKeyEventHandler,
+ WindowRootViewKeyEventHandler windowRootViewKeyEventHandler,
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
@@ -232,8 +229,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
mNotificationInsetsController = notificationInsetsController;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mGlanceableHubContainerController = glanceableHubContainerController;
- mFeatureFlagsClassic = featureFlagsClassic;
- mSysUIKeyEventHandler = sysUIKeyEventHandler;
+ mWindowRootViewKeyEventHandler = windowRootViewKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mQuickSettingsController = quickSettingsController;
@@ -370,6 +366,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
mView.setLayoutInsetsController(mNotificationInsetsController);
+ mView.setWindowRootViewKeyEventHandler(mWindowRootViewKeyEventHandler);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
boolean mUseDragDownHelperForTouch = false;
boolean mLastInterceptWasDragDownHelper = false;
@@ -605,26 +602,6 @@ public class NotificationShadeWindowViewController implements Dumpable {
mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
}
}
-
- @Override
- public boolean interceptMediaKey(KeyEvent event) {
- return mSysUIKeyEventHandler.interceptMediaKey(event);
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- return mSysUIKeyEventHandler.dispatchKeyEventPreIme(event);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- return mSysUIKeyEventHandler.dispatchKeyEvent(event);
- }
-
- @Override
- public void collectKeyEvent(KeyEvent event) {
- mFalsingCollector.onKeyEvent(event);
- }
});
mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 42c63f9fcdb1..c671f7d9db14 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -959,6 +959,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
void setShadeExpansion(float expandedHeight, float expandedFraction) {
mShadeExpandedHeight = expandedHeight;
mShadeExpandedFraction = expandedFraction;
+ mMediaHierarchyManager.setShadeExpandedFraction(expandedFraction);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 96b224fbd4f3..cd224735cc62 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.display.ShadeDisplayPolicyModule
@@ -205,7 +206,18 @@ object ShadeDisplayAwareModule {
@SysUISingleton
@Provides
- fun provideShadePositionRepository(impl: ShadeDisplaysRepositoryImpl): ShadeDisplaysRepository {
+ fun provideShadePositionRepository(
+ impl: MutableShadeDisplaysRepository
+ ): ShadeDisplaysRepository {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ return impl
+ }
+
+ @SysUISingleton
+ @Provides
+ fun provideMutableShadePositionRepository(
+ impl: ShadeDisplaysRepositoryImpl
+ ): MutableShadeDisplaysRepository {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
return impl
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt
index 6d8e8986443e..63b618f72921 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt
@@ -50,7 +50,9 @@ object ShadeExpandsOnStatusBarLongPress {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 55ee27d1027c..150ef3afdd82 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -42,6 +42,7 @@ import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.view.SceneWindowRootView
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.LightRevealScrim
@@ -88,6 +89,7 @@ abstract class ShadeViewProviderModule {
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
qsSceneAdapter: Provider<QSSceneAdapter>,
sceneJankMonitorFactory: SceneJankMonitor.Factory,
+ windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler,
): WindowRootView {
return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
@@ -104,6 +106,7 @@ abstract class ShadeViewProviderModule {
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
qsSceneAdapter = qsSceneAdapter,
sceneJankMonitorFactory = sceneJankMonitorFactory,
+ windowRootViewKeyEventHandler = windowRootViewKeyEventHandler,
)
sceneWindowRootView
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 3513334f2a5c..e18ed83a1e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -22,16 +22,28 @@ import com.android.systemui.shade.display.ShadeDisplayPolicy
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-class FakeShadeDisplayRepository : ShadeDisplaysRepository {
+class FakeShadeDisplayRepository : MutableShadeDisplaysRepository {
private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+ private val _pendingDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
fun setDisplayId(displayId: Int) {
_displayId.value = displayId
}
+ fun setPendingDisplayId(displayId: Int) {
+ _pendingDisplayId.value = displayId
+ }
+
+ override fun onDisplayChangedSucceeded(displayId: Int) {
+ setDisplayId(displayId)
+ }
+
override val displayId: StateFlow<Int>
get() = _displayId
+ override val pendingDisplayId: StateFlow<Int>
+ get() = _pendingDisplayId
+
override val currentPolicy: ShadeDisplayPolicy
get() = FakeShadeDisplayPolicy
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
index 91c92cc8cd2f..ce74cb7d54d2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
@@ -20,7 +20,7 @@ import android.content.IntentFilter
import android.os.UserHandle
import android.safetycenter.SafetyCenterManager
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 2a14ca44386d..7117a23bfb77 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -29,6 +29,7 @@ import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -40,10 +41,28 @@ import kotlinx.coroutines.flow.stateIn
/** Source of truth for the display currently holding the shade. */
interface ShadeDisplaysRepository {
- /** ID of the display which currently hosts the shade */
+ /** ID of the display which currently hosts the shade. */
val displayId: StateFlow<Int>
/** The current policy set. */
val currentPolicy: ShadeDisplayPolicy
+
+ /**
+ * Id of the display that should host the shade.
+ *
+ * If this differs from [displayId], it means there is a shade movement in progress. Classes
+ * that rely on the shade being already moved (and its context/resources updated) should rely on
+ * [displayId]. Classes that need to do work associated with the shade move, should listen at
+ * this.
+ */
+ val pendingDisplayId: StateFlow<Int>
+}
+
+/** Provides a way to set whether the display changed succeeded. */
+interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
+ /**
+ * To be called when the shade changed window, and its resources have been completely updated.
+ */
+ fun onDisplayChangedSucceeded(displayId: Int)
}
/**
@@ -63,7 +82,7 @@ constructor(
@ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
-) : ShadeDisplaysRepository {
+) : MutableShadeDisplaysRepository {
private val policy: StateFlow<ShadeDisplayPolicy> =
globalSettings
@@ -105,10 +124,16 @@ constructor(
override val currentPolicy: ShadeDisplayPolicy
get() = policy.value
- override val displayId: StateFlow<Int> =
+ override val pendingDisplayId: StateFlow<Int> =
keyguardAwareDisplayPolicy.stateIn(
bgScope,
SharingStarted.WhileSubscribed(),
Display.DEFAULT_DISPLAY,
)
+ private val _committedDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+ override val displayId: StateFlow<Int> = _committedDisplayId
+
+ override fun onDisplayChangedSucceeded(displayId: Int) {
+ _committedDisplayId.value = displayId
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
index 4edba2785e89..3b9870881c60 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
@@ -47,14 +47,16 @@ class ShadeDialogContextInteractorImpl
constructor(
@Main private val defaultContext: Context,
private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>,
- private val shadeDisplaysRepository: ShadeDisplaysRepository,
+ private val shadeDisplaysRepository: Provider<ShadeDisplaysRepository>,
@Background private val bgScope: CoroutineScope,
) : CoreStartable, ShadeDialogContextInteractor {
override fun start() {
if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return
bgScope.launchTraced(TAG) {
- shadeDisplaysRepository.displayId
+ shadeDisplaysRepository
+ .get()
+ .displayId
// No need for default display pre-warming.
.filter { it != Display.DEFAULT_DISPLAY }
.collectLatest { displayId ->
@@ -70,7 +72,7 @@ constructor(
if (!ShadeWindowGoesAround.isEnabled) {
return defaultContext
}
- val displayId = shadeDisplaysRepository.displayId.value
+ val displayId = shadeDisplaysRepository.get().displayId.value
return getContextOrDefault(displayId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 9a5c96824e77..0e0f58dc8d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker
import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo
import com.android.systemui.shade.ShadeTraceLogger.t
import com.android.systemui.shade.ShadeTraceLogger.traceReparenting
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -44,6 +45,7 @@ import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
@@ -55,7 +57,7 @@ import kotlinx.coroutines.withTimeoutOrNull
class ShadeDisplaysInteractor
@Inject
constructor(
- private val shadePositionRepository: ShadeDisplaysRepository,
+ private val shadePositionRepository: MutableShadeDisplaysRepository,
@ShadeDisplayAware private val shadeContext: WindowContext,
@ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
@Background private val bgScope: CoroutineScope,
@@ -72,11 +74,14 @@ constructor(
private val hasActiveNotifications: Boolean
get() = activeNotificationsInteractor.areAnyNotificationsPresentValue
+ /** Current display id of the shade window. */
+ val displayId: StateFlow<Int> = shadePositionRepository.displayId
+
override fun start() {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
listenForWindowContextConfigChanges()
bgScope.launchTraced(TAG) {
- shadePositionRepository.displayId.collectLatest { displayId ->
+ shadePositionRepository.pendingDisplayId.collectLatest { displayId ->
moveShadeWindowTo(displayId)
}
}
@@ -108,7 +113,8 @@ constructor(
val currentDisplay = shadeContext.display ?: error("Current shade display is null")
currentId = currentDisplay.displayId
if (currentId == destinationId) {
- error("Trying to move the shade to a display it was already in")
+ Log.w(TAG, "Trying to move the shade to a display ($currentId) it was already in ")
+ return
}
withContext(mainThreadContext) {
@@ -118,6 +124,7 @@ constructor(
reparentToDisplayId(id = destinationId)
}
checkContextDisplayMatchesExpected(destinationId)
+ shadePositionRepository.onDisplayChangedSucceeded(destinationId)
}
}
} catch (e: IllegalStateException) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 81146852aefc..52de0abf7d3c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -56,7 +56,7 @@ constructor(
private val shadeModeInteractor: ShadeModeInteractor,
) : BaseShadeInteractor {
init {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
}
override val shadeExpansion: StateFlow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index dc444ffc2a34..81824f69eddd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -67,7 +67,9 @@ object ShadeWindowGoesAround {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 20b44d73e097..5609326362fc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -26,6 +26,7 @@ import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.animation.scene.OverlayKey
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -86,6 +87,22 @@ constructor(
(ViewGroup, StatusBarLocation) -> BatteryMeterViewController =
batteryMeterViewControllerFactory::create
+ val showClock: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "showClock",
+ initialValue =
+ shouldShowClock(
+ isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
+ overlays = sceneInteractor.currentOverlays.value,
+ ),
+ source =
+ combine(
+ shadeInteractor.isShadeLayoutWide,
+ sceneInteractor.currentOverlays,
+ ::shouldShowClock,
+ ),
+ )
+
val notificationsChipHighlight: HeaderChipHighlight by
hydrator.hydratedStateOf(
traceName = "notificationsChipHighlight",
@@ -114,13 +131,6 @@ constructor(
},
)
- val isShadeLayoutWide: Boolean by
- hydrator.hydratedStateOf(
- traceName = "isShadeLayoutWide",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
@@ -271,6 +281,11 @@ constructor(
}
}
+ private fun shouldShowClock(isShadeLayoutWide: Boolean, overlays: Set<OverlayKey>): Boolean {
+ // Notifications shade on narrow layout renders its own clock. Hide the header clock.
+ return isShadeLayoutWide || Overlays.NotificationsShade !in overlays
+ }
+
private fun getFormatFromPattern(pattern: String?): DateFormat {
val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault())
format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index f45971b57b65..2bacee12db8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -86,7 +86,7 @@ open class BlurUtils @Inject constructor(
*/
fun prepareBlur(viewRootImpl: ViewRootImpl?, radius: Int) {
if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid ||
- !supportsBlursOnWindows() || earlyWakeupEnabled
+ !shouldBlur(radius) || earlyWakeupEnabled
) {
return
}
@@ -113,7 +113,7 @@ open class BlurUtils @Inject constructor(
return
}
createTransaction().use {
- if (supportsBlursOnWindows()) {
+ if (shouldBlur(radius)) {
it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
if (!earlyWakeupEnabled && lastAppliedBlur == 0 && radius != 0) {
Trace.asyncTraceForTrackBegin(
@@ -142,6 +142,14 @@ open class BlurUtils @Inject constructor(
return SurfaceControl.Transaction()
}
+ private fun shouldBlur(radius: Int): Boolean {
+ return supportsBlursOnWindows() ||
+ ((Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) &&
+ supportsBlursOnWindowsBase() &&
+ lastAppliedBlur > 0 &&
+ radius == 0)
+ }
+
/**
* If this device can render blurs.
*
@@ -149,8 +157,11 @@ open class BlurUtils @Inject constructor(
* @return {@code true} when supported.
*/
open fun supportsBlursOnWindows(): Boolean {
+ return supportsBlursOnWindowsBase() && crossWindowBlurListeners.isCrossWindowBlurEnabled
+ }
+
+ private fun supportsBlursOnWindowsBase(): Boolean {
return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() &&
- crossWindowBlurListeners.isCrossWindowBlurEnabled() &&
!SystemProperties.getBoolean("persist.sysui.disableBlur", false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index bfd512fa6a2d..ef0660fbcd1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -566,12 +566,11 @@ public final class KeyboardShortcutListSearch {
Arrays.asList(
Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))),
/* Back: go back to previous state (back button) */
- /* Meta + Escape, Meta + backspace, Meta + left arrow */
+ /* Meta + Escape, Meta + left arrow */
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_go_back),
Arrays.asList(
Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON),
- Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON),
Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))),
/* Take a full screenshot: Meta + S */
new ShortcutKeyGroupMultiMappingInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 657c86b10f16..e66b21866902 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1088,14 +1088,18 @@ public class KeyguardIndicationController {
if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
mWakeLock.setAcquired(true);
+ final KeyguardIndication.Builder builder = new KeyguardIndication.Builder()
+ .setMessage(newIndication)
+ .setTextColor(ColorStateList.valueOf(
+ useMisalignmentColor
+ ? mContext.getColor(R.color.misalignment_text_color)
+ : Color.WHITE));
+ if (mBiometricMessage != null && newIndication == mBiometricMessage) {
+ builder.setForceAccessibilityLiveRegionAssertive();
+ }
+
mTopIndicationView.switchIndication(newIndication,
- new KeyguardIndication.Builder()
- .setMessage(newIndication)
- .setTextColor(ColorStateList.valueOf(
- useMisalignmentColor
- ? mContext.getColor(R.color.misalignment_text_color)
- : Color.WHITE))
- .build(),
+ builder.build(),
true, () -> mWakeLock.setAcquired(false));
}
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index c2e355d07e9c..03c191e40ccf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -22,6 +22,7 @@ import android.app.Flags;
import android.app.Notification;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -31,6 +32,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.R;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.ConversationLayout;
@@ -39,6 +42,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.util.ArrayList;
import java.util.HashSet;
@@ -214,7 +218,7 @@ public class NotificationGroupingUtil {
}
// in case no view is visible we make sure the time is visible
int timeVisibility = !hasVisibleText
- || row.getEntry().getSbn().getNotification().showsTime()
+ || showsTime(row)
? View.VISIBLE : View.GONE;
time.setVisibility(timeVisibility);
View left = null;
@@ -243,6 +247,17 @@ public class NotificationGroupingUtil {
}
}
+ @VisibleForTesting
+ boolean showsTime(ExpandableNotificationRow row) {
+ StatusBarNotification sbn;
+ if (NotificationBundleUi.isEnabled()) {
+ sbn = row.getEntryAdapter() != null ? row.getEntryAdapter().getSbn() : null;
+ } else {
+ sbn = row.getEntry().getSbn();
+ }
+ return (sbn != null && sbn.getNotification().showsTime());
+ }
+
/**
* Reset the modifications to this row for removing it from the group.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 3180a06ae787..fdcd39d1d616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -34,7 +34,7 @@ public interface NotificationLockscreenUserManager {
value = {
REDACTION_TYPE_NONE,
REDACTION_TYPE_PUBLIC,
- REDACTION_TYPE_SENSITIVE_CONTENT})
+ REDACTION_TYPE_OTP})
@interface RedactionType {}
/**
@@ -52,7 +52,7 @@ public interface NotificationLockscreenUserManager {
* Indicates that a notification should have its main content redacted, due to detected
* sensitive content, such as a One-Time Password
*/
- int REDACTION_TYPE_SENSITIVE_CONTENT = 1 << 1;
+ int REDACTION_TYPE_OTP = 1 << 1;
/**
* @param userId user Id
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index c9b96e9871ed..339f898be251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -76,6 +76,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController.StateList
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -794,7 +795,7 @@ public class NotificationLockscreenUserManagerImpl implements
}
if (shouldShowSensitiveContentRedactedView(ent)) {
- return REDACTION_TYPE_SENSITIVE_CONTENT;
+ return REDACTION_TYPE_OTP;
}
return REDACTION_TYPE_NONE;
}
@@ -810,6 +811,10 @@ public class NotificationLockscreenUserManagerImpl implements
* lock time.
*/
private boolean shouldShowSensitiveContentRedactedView(NotificationEntry ent) {
+ if (android.app.Flags.redactionOnLockscreenMetrics()) {
+ return shouldShowSensitiveContentRedactedViewWithLog(ent);
+ }
+
if (!LockscreenOtpRedaction.isEnabled()) {
return false;
}
@@ -846,11 +851,76 @@ public class NotificationLockscreenUserManagerImpl implements
return true;
}
+ /*
+ * We show the sensitive content redaction view if
+ * 1. The feature is enabled
+ * 2. The notification has the `hasSensitiveContent` ranking variable set to true
+ * 3. The device is locked
+ * 4. The device is NOT connected to Wifi
+ * 5. The device has not connected to Wifi since receiving the notification
+ * 6. The notification arrived at least LOCK_TIME_FOR_SENSITIVE_REDACTION_MS after the last
+ * lock time.
+ *
+ * This version of the method logs a metric about the request.
+ */
+ private boolean shouldShowSensitiveContentRedactedViewWithLog(NotificationEntry ent) {
+ if (!LockscreenOtpRedaction.isEnabled()) {
+ return false;
+ }
+
+ if (ent.getRanking() == null || !ent.getRanking().hasSensitiveContent()) {
+ return false;
+ }
+
+ long notificationWhen = ent.getSbn().getNotification().getWhen();
+ long notificationTime = getEarliestNotificationTime(ent);
+ boolean locked = mLocked.get();
+ long lockTime = mLastLockTime.get();
+ boolean wifiConnected = mConnectedToWifi.get();
+ long wifiConnectionTime = mLastWifiConnectionTime.get();
+
+ boolean shouldRedact = true;
+ if (!locked) {
+ shouldRedact = false;
+ }
+
+ if (!mRedactOtpOnWifi.get()) {
+ if (wifiConnected) {
+ shouldRedact = false;
+ }
+
+ // If the device has connected to wifi since receiving the notification, do not redact
+ if (notificationTime < wifiConnectionTime) {
+ shouldRedact = false;
+ }
+ }
+
+ // If the lock screen was not already locked for at least mOtpRedactionRequiredLockTimeMs
+ // when this notification arrived, do not redact
+ long latestTimeForRedaction = lockTime + mOtpRedactionRequiredLockTimeMs.get();
+
+ if (notificationTime < latestTimeForRedaction) {
+ shouldRedact = false;
+ }
+
+ int whenAndEarliestDiff = clampLongToIntRange(notificationWhen - notificationTime);
+ int earliestAndLockDiff = clampLongToIntRange(lockTime - notificationTime);
+ int earliestAndWifiDiff = clampLongToIntRange(wifiConnectionTime - notificationTime);
+ SysUiStatsLog.write(SysUiStatsLog.OTP_NOTIFICATION_DISPLAYED, shouldRedact,
+ whenAndEarliestDiff, locked, earliestAndLockDiff, wifiConnected,
+ earliestAndWifiDiff);
+ return shouldRedact;
+ }
+
+ private int clampLongToIntRange(long toConvert) {
+ return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, toConvert));
+ }
+
// Get the earliest time the user might have seen this notification. This is either the
// notification's "when" time, or the notification entry creation time
private long getEarliestNotificationTime(NotificationEntry notif) {
long notifWhenWallClock = notif.getSbn().getNotification().getWhen();
- long creationTimeDelta = SystemClock.elapsedRealtime() - notif.getCreationTime();
+ long creationTimeDelta = SystemClock.uptimeMillis() - notif.getCreationTime();
long creationTimeWallClock = System.currentTimeMillis() - creationTimeDelta;
return Math.min(notifWhenWallClock, creationTimeWallClock);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 0d34bdc7e477..041ed6504634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -59,11 +59,13 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -134,20 +136,21 @@ public class NotificationRemoteInputManager implements CoreStartable {
view.getTag(com.android.internal.R.id.notification_action_index_tag);
final ExpandableNotificationRow row = getNotificationRowForParent(view.getParent());
- final NotificationEntry entry = getNotificationForParent(view.getParent());
- mLogger.logInitialClick(
- row != null ? row.getLoggingKey() : null, actionIndex, pendingIntent);
+ if (row == null) {
+ return false;
+ }
+ mLogger.logInitialClick(row.getLoggingKey(), actionIndex, pendingIntent);
if (handleRemoteInput(view, pendingIntent)) {
- mLogger.logRemoteInputWasHandled(
- row != null ? row.getLoggingKey() : null, actionIndex);
+ mLogger.logRemoteInputWasHandled(row.getLoggingKey(), actionIndex);
return true;
}
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
- logActionClick(view, entry, pendingIntent);
+ Notification.Action action = getActionFromView(view, row, pendingIntent);
+ logActionClick(view, row.getKey(), action);
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
@@ -156,33 +159,47 @@ public class NotificationRemoteInputManager implements CoreStartable {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
- Notification.Action action = getActionFromView(view, entry, pendingIntent);
return mCallback.handleRemoteViewClick(view, pendingIntent,
action == null ? false : action.isAuthenticationRequired(), actionIndex, () -> {
Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
mLogger.logStartingIntentWithDefaultHandler(
- row != null ? row.getLoggingKey() : null, pendingIntent, actionIndex);
+ row.getLoggingKey(), pendingIntent, actionIndex);
boolean started = RemoteViews.startPendingIntent(view, pendingIntent, options);
- if (started) releaseNotificationIfKeptForRemoteInputHistory(entry);
+ if (started) {
+ if (NotificationBundleUi.isEnabled()) {
+ releaseNotificationIfKeptForRemoteInputHistory(row.getEntryAdapter());
+ } else {
+ releaseNotificationIfKeptForRemoteInputHistory(row.getEntry());
+ }
+ }
return started;
});
}
private @Nullable Notification.Action getActionFromView(View view,
- NotificationEntry entry, PendingIntent actionIntent) {
+ ExpandableNotificationRow row, PendingIntent actionIntent) {
Integer actionIndex = (Integer)
view.getTag(com.android.internal.R.id.notification_action_index_tag);
if (actionIndex == null) {
return null;
}
- if (entry == null) {
+ StatusBarNotification statusBarNotification = null;
+ if (NotificationBundleUi.isEnabled()) {
+ if (row.getEntryAdapter() != null) {
+ statusBarNotification = row.getEntryAdapter().getSbn();
+ }
+ } else {
+ if (row.getEntry() != null) {
+ statusBarNotification = row.getEntry().getSbn();
+ }
+ }
+ if (statusBarNotification == null) {
Log.w(TAG, "Couldn't determine notification for click.");
return null;
}
// Notification may be updated before this function is executed, and thus play safe
// here and verify that the action object is still the one that where the click happens.
- StatusBarNotification statusBarNotification = entry.getSbn();
Notification.Action[] actions = statusBarNotification.getNotification().actions;
if (actions == null || actionIndex >= actions.length) {
Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
@@ -199,14 +216,12 @@ public class NotificationRemoteInputManager implements CoreStartable {
private void logActionClick(
View view,
- NotificationEntry entry,
- PendingIntent actionIntent) {
- Notification.Action action = getActionFromView(view, entry, actionIntent);
+ String key,
+ Notification.Action action) {
if (action == null) {
return;
}
ViewParent parent = view.getParent();
- String key = entry.getSbn().getKey();
int buttonIndex = -1;
// If this is a default template, determine the index of the button.
if (view.getId() == com.android.internal.R.id.action0 &&
@@ -214,20 +229,10 @@ public class NotificationRemoteInputManager implements CoreStartable {
ViewGroup actionGroup = (ViewGroup) parent;
buttonIndex = actionGroup.indexOfChild(view);
}
- final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
+ final NotificationVisibility nv = mVisibilityProvider.obtain(key, true);
mClickNotifier.onNotificationActionClick(key, buttonIndex, action, nv, false);
}
- private NotificationEntry getNotificationForParent(ViewParent parent) {
- while (parent != null) {
- if (parent instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) parent).getEntry();
- }
- parent = parent.getParent();
- }
- return null;
- }
-
private @Nullable ExpandableNotificationRow getNotificationRowForParent(ViewParent parent) {
while (parent != null) {
if (parent instanceof ExpandableNotificationRow) {
@@ -394,11 +399,21 @@ public class NotificationRemoteInputManager implements CoreStartable {
}
}
+ /**
+ * Use {@link com.android.systemui.statusbar.notification.row.NotificationActionClickManager}
+ * instead
+ */
public void addActionPressListener(Consumer<NotificationEntry> listener) {
+ NotificationBundleUi.assertInLegacyMode();
mActionPressListeners.addIfAbsent(listener);
}
+ /**
+ * Use {@link com.android.systemui.statusbar.notification.row.NotificationActionClickManager}
+ * instead
+ */
public void removeActionPressListener(Consumer<NotificationEntry> listener) {
+ NotificationBundleUi.assertInLegacyMode();
mActionPressListeners.remove(listener);
}
@@ -681,12 +696,30 @@ public class NotificationRemoteInputManager implements CoreStartable {
* (after unlock, if applicable), and will then wait a short time to allow the app to update the
* notification in response to the action.
*/
+ private void releaseNotificationIfKeptForRemoteInputHistory(EntryAdapter entryAdapter) {
+ if (entryAdapter == null) {
+ return;
+ }
+ if (mRemoteInputListener != null) {
+ mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(
+ entryAdapter.getKey());
+ }
+ entryAdapter.onNotificationActionClicked();
+ }
+
+ /**
+ * Checks if the notification is being kept due to the user sending an inline reply, and if
+ * so, releases that hold. This is called anytime an action on the notification is dispatched
+ * (after unlock, if applicable), and will then wait a short time to allow the app to update the
+ * notification in response to the action.
+ */
private void releaseNotificationIfKeptForRemoteInputHistory(NotificationEntry entry) {
+ NotificationBundleUi.assertInLegacyMode();
if (entry == null) {
return;
}
if (mRemoteInputListener != null) {
- mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry);
+ mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry.getKey());
}
for (Consumer<NotificationEntry> listener : mActionPressListeners) {
listener.accept(entry);
@@ -866,7 +899,7 @@ public class NotificationRemoteInputManager implements CoreStartable {
boolean isNotificationKeptForRemoteInputHistory(@NonNull String key);
/** Called on user interaction to end lifetime extension for history */
- void releaseNotificationIfKeptForRemoteInputHistory(@NonNull NotificationEntry entry);
+ void releaseNotificationIfKeptForRemoteInputHistory(@NonNull String entryKey);
/** Called when the RemoteInputController is attached to the manager */
void setRemoteInputController(@NonNull RemoteInputController remoteInputController);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 84266e805773..472dc823423e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.content.Context
-import android.content.res.Configuration
import android.os.SystemClock
import android.util.IndentingPrintWriter
import android.util.Log
@@ -42,27 +40,25 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.sign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Responsible for blurring the notification shade window, and applying a zoom effect to the
@@ -82,13 +78,11 @@ constructor(
private val wallpaperInteractor: WallpaperInteractor,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val dozeParameters: DozeParameters,
- @ShadeDisplayAware private val context: Context,
- private val splitShadeStateController: SplitShadeStateController,
+ private val shadeModeInteractor: ShadeModeInteractor,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
private val appZoomOutOptional: Optional<AppZoomOut>,
@Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
- configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
@@ -97,6 +91,7 @@ constructor(
private const val MIN_VELOCITY = -MAX_VELOCITY
private const val INTERACTION_BLUR_FRACTION = 0.8f
private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION
+ private const val TRANSITION_THRESHOLD = 0.98f
private const val TAG = "DepthController"
}
@@ -109,7 +104,6 @@ constructor(
private var isOpen: Boolean = false
private var isBlurred: Boolean = false
private var listeners = mutableListOf<DepthListener>()
- private var inSplitShade: Boolean = false
private var prevTracking: Boolean = false
private var prevTimestamp: Long = -1
@@ -163,6 +157,9 @@ constructor(
/**
* When launching an app from the shade, the animations progress should affect how blurry the
* shade is, overriding the expansion amount.
+ *
+ * TODO(b/399617511): remove this once [Flags.notificationShadeBlur] is launched and the Shade
+ * closing is actually instantaneous.
*/
var blursDisabledForAppLaunch: Boolean = false
set(value) {
@@ -192,8 +189,12 @@ constructor(
return
}
- shadeAnimation.animateTo(0)
- shadeAnimation.finishIfRunning()
+ if (Flags.notificationShadeBlur()) {
+ shadeAnimation.skipTo(0)
+ } else {
+ shadeAnimation.animateTo(0)
+ shadeAnimation.finishIfRunning()
+ }
}
@Deprecated(
message =
@@ -225,16 +226,10 @@ constructor(
scheduleUpdate()
}
- /** Blur radius of the wake-up animation on this frame. */
- private var wakeBlurRadius = 0f
- set(value) {
- if (field == value) return
- field = value
- scheduleUpdate()
- }
+ private data class WakeAndUnlockBlurData(val radius: Float, val useZoom: Boolean = true)
- /** Blur radius of the unlock animation on this frame. */
- private var unlockBlurRadius = 0f
+ /** Blur radius of the wake and unlock animation on this frame, and whether to zoom out. */
+ private var wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f)
set(value) {
if (field == value) return
field = value
@@ -261,7 +256,7 @@ constructor(
ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) * shadeExpansion
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
- var shadeRadius = max(combinedBlur, max(wakeBlurRadius, unlockBlurRadius))
+ var shadeRadius = max(combinedBlur, wakeAndUnlockBlurData.radius)
if (areBlursDisabledForAppLaunch || blursDisabledForUnlock) {
shadeRadius = 0f
@@ -270,7 +265,9 @@ constructor(
var blur = shadeRadius.toInt()
// If the blur comes from waking up, we don't want to zoom out the background
val zoomOut =
- if (shadeRadius != wakeBlurRadius) blurRadiusToZoomOut(blurRadius = shadeRadius) else 0f
+ if (shadeRadius != wakeAndUnlockBlurData.radius|| wakeAndUnlockBlurData.useZoom)
+ blurRadiusToZoomOut(blurRadius = shadeRadius)
+ else 0f
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!Flags.notificationShadeBlur()) {
@@ -290,7 +287,7 @@ constructor(
private fun blurRadiusToZoomOut(blurRadius: Float): Float {
var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(blurRadius))
- if (inSplitShade) {
+ if (shadeModeInteractor.isSplitShade) {
zoomOut = 0f
}
@@ -355,14 +352,14 @@ constructor(
startDelay = keyguardStateController.keyguardFadingAwayDelay
interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener { animation: ValueAnimator ->
- unlockBlurRadius =
- blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
+ wakeAndUnlockBlurData =
+ WakeAndUnlockBlurData(blurUtils.blurRadiusOfRatio(animation.animatedValue as Float))
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
keyguardAnimator = null
- unlockBlurRadius = 0f
+ wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f)
}
}
)
@@ -404,12 +401,15 @@ constructor(
}
private fun updateWakeBlurRadius(ratio: Float) {
- wakeBlurRadius =
- if (!wallpaperSupportsAmbientMode) {
- 0f
- } else {
- blurUtils.blurRadiusOfRatio(ratio)
- }
+ wakeAndUnlockBlurData = WakeAndUnlockBlurData(getNewWakeBlurRadius(ratio), false)
+ }
+
+ private fun getNewWakeBlurRadius(ratio: Float): Float {
+ return if (!wallpaperSupportsAmbientMode) {
+ 0f
+ } else {
+ blurUtils.blurRadiusOfRatio(ratio)
+ }
}
init {
@@ -425,18 +425,14 @@ constructor(
}
shadeAnimation.setStiffness(SpringForce.STIFFNESS_LOW)
shadeAnimation.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- updateResources()
- configurationController.addCallback(
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
- }
- }
- )
applicationScope.launch {
wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported ->
wallpaperSupportsAmbientMode = supported
- updateWakeBlurRadius(prevDozeAmount)
+ if (getNewWakeBlurRadius(prevDozeAmount) == wakeAndUnlockBlurData.radius
+ && !wakeAndUnlockBlurData.useZoom) {
+ // Update wake and unlock radius only if the previous value comes from wake-up.
+ updateWakeBlurRadius(prevDozeAmount)
+ }
}
}
initBlurListeners()
@@ -458,10 +454,6 @@ constructor(
}
}
- private fun updateResources() {
- inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources)
- }
-
fun addListener(listener: DepthListener) {
listeners.add(listener)
}
@@ -515,6 +507,22 @@ constructor(
scheduleUpdate()
}
+ fun onTransitionAnimationProgress(progress: Float) {
+ if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) return
+ // Because the Shade takes a few frames to actually trigger the unblur after a transition
+ // has ended, we need to disable it manually, or the opening window itself will be blurred
+ // for a few frames due to relative ordering. We do this towards the end, so that the
+ // window is already covering the background and the unblur is not visible.
+ if (progress >= TRANSITION_THRESHOLD && shadeAnimation.radius > 0) {
+ blursDisabledForAppLaunch = true
+ }
+ }
+
+ fun onTransitionAnimationEnd() {
+ if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) return
+ blursDisabledForAppLaunch = false
+ }
+
private fun updateShadeAnimationBlur(
expansion: Float,
tracking: Boolean,
@@ -613,8 +621,8 @@ constructor(
it.println("shouldApplyShadeBlur: ${shouldApplyShadeBlur()}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
- it.println("wakeBlur: $wakeBlurRadius")
- it.println("unlockBlur: $wakeBlurRadius")
+ it.println("wakeAndUnlockBlurRadius: ${wakeAndUnlockBlurData.radius}")
+ it.println("wakeAndUnlockBlurUsesZoom: ${wakeAndUnlockBlurData.useZoom}")
it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
it.println("appLaunchTransitionIsInProgress: $appLaunchTransitionIsInProgress")
it.println("qsPanelExpansion: $qsPanelExpansion")
@@ -660,6 +668,20 @@ constructor(
springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 }
}
+ /**
+ * Starts an animation to [newRadius], or updates the current one if already ongoing.
+ * IMPORTANT: do NOT use this method + [finishIfRunning] to instantaneously change the value
+ * of the animation. The change will NOT be instantaneous. Use [skipTo] instead.
+ *
+ * Explanation:
+ * 1. If idle, [SpringAnimation.animateToFinalPosition] requests a start to the animation.
+ * 2. On the first frame after an idle animation is requested to start, the animation simply
+ * acquires the starting value and does nothing else.
+ * 3. [SpringAnimation.skipToEnd] requests a fast-forward to the end value, but this happens
+ * during calculation of the next animation value. Because on the first frame no such
+ * calculation happens (point #2), there is one lagging frame where we still see the old
+ * value.
+ */
fun animateTo(newRadius: Int) {
if (pendingRadius == newRadius) {
return
@@ -668,6 +690,19 @@ constructor(
springAnimation.animateToFinalPosition(newRadius.toFloat())
}
+ /**
+ * Instantaneously set a new blur radius to this animation. Always use this instead of
+ * [animateTo] and [finishIfRunning] to make sure that the change takes effect in the next
+ * frame. See the doc for [animateTo] for an explanation.
+ */
+ fun skipTo(newRadius: Int) {
+ if (pendingRadius == newRadius) return
+ pendingRadius = newRadius
+ springAnimation.cancel()
+ springAnimation.setStartValue(newRadius.toFloat())
+ springAnimation.animateToFinalPosition(newRadius.toFloat())
+ }
+
fun finishIfRunning() {
if (springAnimation.isRunning) {
springAnimation.skipToEnd()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index b2764e1a2302..6d3c12d139db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -18,12 +18,14 @@ per-file *Keyguard* = file:../keyguard/OWNERS
per-file *Notification* = file:notification/OWNERS
# Files that control blur effects on shade
per-file *NotificationShadeDepth* = set noparent
-per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com
+per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com, tracyzhou@google.com
per-file *NotificationShadeDepth* = file:../keyguard/OWNERS
per-file *Blur* = set noparent
-per-file *Blur* = shanh@google.com, rahulbanerjee@google.com
+per-file *Blur* = shanh@google.com, rahulbanerjee@google.com, tracyzhou@google.com
# Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*)
per-file *Mode* = file:notification/OWNERS
+per-file *SmartReply* = set noparent
+per-file *SmartReply* = file:notification/OWNERS
per-file *RemoteInput* = set noparent
per-file *RemoteInput* = file:notification/OWNERS
per-file *EmptyShadeView* = set noparent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
index 6148b407d3bf..8fc95092be10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.plugins.statusbar.StatusBarStateController
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt
new file mode 100644
index 000000000000..176419454c21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.chips
+
+import com.android.systemui.Flags
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+
+/** Helper for reading or using the status_bar_chips_return_animations flag state. */
+object StatusBarChipsReturnAnimations {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS
+
+ /** Is the feature enabled. */
+ @JvmStatic
+ inline val isEnabled
+ get() = StatusBarChipsModernization.isEnabled && Flags.statusBarChipsReturnAnimations()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index f466278e15a8..7e7031200988 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.app.PendingIntent
import android.content.Context
import android.view.View
import com.android.internal.jank.Cuj
@@ -31,6 +32,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -42,8 +44,10 @@ import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCall
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -59,67 +63,110 @@ constructor(
private val activityStarter: ActivityStarter,
@StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
- override val chip: StateFlow<OngoingActivityChipModel> =
- interactor.ongoingCallState
- .map { state ->
- when (state) {
- is OngoingCallModel.NoCall,
- is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Inactive()
- is OngoingCallModel.InCall -> {
- val key = state.notificationKey
- val contentDescription = getContentDescription(state.appName)
- val icon =
- if (state.notificationIconView != null) {
- StatusBarConnectedDisplays.assertInLegacyMode()
- OngoingActivityChipModel.ChipIcon.StatusBarView(
- state.notificationIconView,
- contentDescription,
- )
- } else if (StatusBarConnectedDisplays.isEnabled) {
- OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
- state.notificationKey,
- contentDescription,
- )
+ private val chipWithReturnAnimation: StateFlow<OngoingActivityChipModel> =
+ if (StatusBarChipsReturnAnimations.isEnabled) {
+ interactor.ongoingCallState
+ .map { state ->
+ when (state) {
+ is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
+ is OngoingCallModel.InCall ->
+ prepareChip(state, systemClock, isHidden = state.isAppVisible)
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ OngoingActivityChipModel.Inactive(),
+ )
+ } else {
+ MutableStateFlow(OngoingActivityChipModel.Inactive()).asStateFlow()
+ }
+
+ private val chipLegacy: StateFlow<OngoingActivityChipModel> =
+ if (!StatusBarChipsReturnAnimations.isEnabled) {
+ interactor.ongoingCallState
+ .map { state ->
+ when (state) {
+ is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
+ is OngoingCallModel.InCall ->
+ if (state.isAppVisible) {
+ OngoingActivityChipModel.Inactive()
} else {
- OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
+ prepareChip(state, systemClock, isHidden = false)
}
-
- val colors = ColorsModel.AccentThemed
-
- // This block mimics OngoingCallController#updateChip.
- if (state.startTimeMs <= 0L) {
- // If the start time is invalid, don't show a timer and show just an
- // icon. See b/192379214.
- OngoingActivityChipModel.Active.IconOnly(
- key = key,
- icon = icon,
- colors = colors,
- onClickListenerLegacy = getOnClickListener(state),
- clickBehavior = getClickBehavior(state),
- )
- } else {
- val startTimeInElapsedRealtime =
- state.startTimeMs - systemClock.currentTimeMillis() +
- systemClock.elapsedRealtime()
- OngoingActivityChipModel.Active.Timer(
- key = key,
- icon = icon,
- colors = colors,
- startTimeMs = startTimeInElapsedRealtime,
- onClickListenerLegacy = getOnClickListener(state),
- clickBehavior = getClickBehavior(state),
- )
- }
}
}
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ OngoingActivityChipModel.Inactive(),
+ )
+ } else {
+ MutableStateFlow(OngoingActivityChipModel.Inactive()).asStateFlow()
+ }
+
+ override val chip: StateFlow<OngoingActivityChipModel> =
+ if (StatusBarChipsReturnAnimations.isEnabled) {
+ chipWithReturnAnimation
+ } else {
+ chipLegacy
+ }
+
+ /** Builds an [OngoingActivityChipModel.Active] from all the relevant information. */
+ private fun prepareChip(
+ state: OngoingCallModel.InCall,
+ systemClock: SystemClock,
+ isHidden: Boolean,
+ ): OngoingActivityChipModel.Active {
+ val key = state.notificationKey
+ val contentDescription = getContentDescription(state.appName)
+ val icon =
+ if (state.notificationIconView != null) {
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ OngoingActivityChipModel.ChipIcon.StatusBarView(
+ state.notificationIconView,
+ contentDescription,
+ )
+ } else if (StatusBarConnectedDisplays.isEnabled) {
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
+ state.notificationKey,
+ contentDescription,
+ )
+ } else {
+ OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Inactive())
- private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
- if (state.intent == null) {
- return null
+ val colors = ColorsModel.AccentThemed
+
+ // This block mimics OngoingCallController#updateChip.
+ if (state.startTimeMs <= 0L) {
+ // If the start time is invalid, don't show a timer and show just an icon.
+ // See b/192379214.
+ return OngoingActivityChipModel.Active.IconOnly(
+ key = key,
+ icon = icon,
+ colors = colors,
+ onClickListenerLegacy = getOnClickListener(state.intent),
+ clickBehavior = getClickBehavior(state.intent),
+ isHidden = isHidden,
+ )
+ } else {
+ val startTimeInElapsedRealtime =
+ state.startTimeMs - systemClock.currentTimeMillis() + systemClock.elapsedRealtime()
+ return OngoingActivityChipModel.Active.Timer(
+ key = key,
+ icon = icon,
+ colors = colors,
+ startTimeMs = startTimeInElapsedRealtime,
+ onClickListenerLegacy = getOnClickListener(state.intent),
+ clickBehavior = getClickBehavior(state.intent),
+ isHidden = isHidden,
+ )
}
+ }
+ private fun getOnClickListener(intent: PendingIntent?): View.OnClickListener? {
+ if (intent == null) return null
return View.OnClickListener { view ->
StatusBarChipsModernization.assertInLegacyMode()
logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
@@ -127,7 +174,7 @@ constructor(
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
// This mimics OngoingCallController#updateChipClickListener.
activityStarter.postStartActivityDismissingKeyguard(
- state.intent,
+ intent,
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
@@ -136,23 +183,18 @@ constructor(
}
}
- private fun getClickBehavior(
- state: OngoingCallModel.InCall
- ): OngoingActivityChipModel.ClickBehavior =
- if (state.intent == null) {
+ private fun getClickBehavior(intent: PendingIntent?): OngoingActivityChipModel.ClickBehavior =
+ if (intent == null) {
OngoingActivityChipModel.ClickBehavior.None
} else {
OngoingActivityChipModel.ClickBehavior.ExpandAction(
onClick = { expandable ->
- StatusBarChipsModernization.assertInNewMode()
+ StatusBarChipsModernization.unsafeAssertInNewMode()
val animationController =
expandable.activityTransitionController(
Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
)
- activityStarter.postStartActivityDismissingKeyguard(
- state.intent,
- animationController,
- )
+ activityStarter.postStartActivityDismissingKeyguard(intent, animationController)
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index d8c3e2546a8f..a0de879845d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -56,7 +56,7 @@ constructor(
private val logger = Logger(logBuffer, "Notif".pad())
// [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the
// top-level tag. It should instead be provided as the first string in each log message.
- private val extraLogTag = "SingleChipInteractor[key=$key]"
+ private val extraLogTag = "SingleNotifChipInteractor[key=$key][id=${hashCode()}]"
init {
if (startingModel.promotedContent == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index edb44185459c..9380dfe32bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -45,6 +46,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
/** An interactor for the notification chips shown in the status bar. */
@SysUISingleton
@@ -73,7 +75,7 @@ constructor(
_promotedNotificationChipTapEvent.asSharedFlow()
suspend fun onPromotedNotificationChipTapped(key: String) {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
_promotedNotificationChipTapEvent.emit(key)
}
@@ -147,28 +149,42 @@ constructor(
*/
val allNotificationChips: Flow<List<NotificationChipModel>> =
if (StatusBarNotifChips.isEnabled) {
- // For all our current interactors...
- // TODO(b/364653005): When a promoted notification is added or removed, each individual
- // interactor's [notificationChip] flow becomes un-collected then re-collected, which
- // can cause some flows to remove then add callbacks when they don't need to. Is there a
- // better structure for this? Maybe Channels or a StateFlow with a short timeout?
- promotedNotificationInteractors.flatMapLatest { interactors ->
- if (interactors.isNotEmpty()) {
- // Combine each interactor's [notificationChip] flow...
- val allNotificationChips: List<Flow<NotificationChipModel?>> =
- interactors.map { interactor -> interactor.notificationChip }
- combine(allNotificationChips) {
+ // For all our current interactors...
+ promotedNotificationInteractors.flatMapLatest { interactors ->
+ if (interactors.isNotEmpty()) {
+ // Combine each interactor's [notificationChip] flow...
+ val allNotificationChips: List<Flow<NotificationChipModel?>> =
+ interactors.map { interactor -> interactor.notificationChip }
+ combine(allNotificationChips) {
// ... and emit just the non-null & sorted chips
it.filterNotNull().sortedWith(chipComparator)
}
- .logSort()
- } else {
- flowOf(emptyList())
+ } else {
+ flowOf(emptyList())
+ }
}
+ } else {
+ flowOf(emptyList())
}
- } else {
- flowOf(emptyList())
- }
+ .distinctUntilChanged()
+ .logSort()
+ .stateIn(
+ backgroundScope,
+ SharingStarted.WhileSubscribed(
+ // When a promoted notification is added or removed, the `.flatMapLatest` above
+ // will stop collection and then re-start collection on each individual
+ // interactor's flow. (It will happen even for a chip that didn't change.) We
+ // don't want the individual interactor flows to stop then re-start because they
+ // may be maintaining values that would get thrown away when collection stops
+ // (like an app's last visible time).
+ // stopTimeoutMillis ensures we maintain those values even during the brief
+ // moment (1-2ms) when `.flatMapLatest` causes collect to stop then immediately
+ // restart.
+ // Note: Channels could also work to solve this.
+ stopTimeoutMillis = 1000
+ ),
+ initialValue = emptyList(),
+ )
/** Emits the notifications that should actually be *shown* as chips in the status bar. */
val shownNotificationChips: Flow<List<NotificationChipModel>> =
@@ -199,14 +215,14 @@ constructor(
}
private fun Flow<List<NotificationChipModel>>.logSort(): Flow<List<NotificationChipModel>> {
- return this.distinctUntilChanged().onEach { chips ->
+ return this.onEach { chips ->
val logString =
chips.joinToString {
"{key=${it.key}. " +
"lastVisibleAppTime=${it.lastAppVisibleTime}. " +
"creationTime=${it.creationTime}}"
}
- logger.d({ "Sorted chips: $str1" }) { str1 = logString }
+ logger.d({ "Sorted notif chips: $str1" }) { str1 = logString }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
index 47ffbafe3735..6431f303089f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
@@ -50,7 +50,9 @@ object StatusBarNotifChips {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 994357f909e4..1a802d634894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -71,7 +71,7 @@ constructor(
private fun NotificationChipModel.toActivityChipModel(
headsUpState: TopPinnedState
): OngoingActivityChipModel.Active {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
val contentDescription = getContentDescription(this.appName)
val icon =
if (this.statusBarChipIconView != null) {
@@ -81,7 +81,7 @@ constructor(
contentDescription,
)
} else {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
this.key,
contentDescription,
@@ -103,7 +103,7 @@ constructor(
}
val clickBehavior =
OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification({
- StatusBarChipsModernization.assertInNewMode()
+ StatusBarChipsModernization.unsafeAssertInNewMode()
clickListener.invoke()
})
@@ -141,7 +141,7 @@ constructor(
// When we're promoting notifications automatically, the `when` time set on the
// notification will likely just be set to the current time, which would cause the chip
// to always show "now". We don't want early testers to get that experience since it's
- // not what will happen at launch, so just don't show any time.
+ // not what will happen at launch, so just don't show any time.onometerstate
return OngoingActivityChipModel.Active.IconOnly(
this.key,
icon,
@@ -194,14 +194,14 @@ constructor(
}
}
is PromotedNotificationContentModel.When.Chronometer -> {
- // TODO(b/364653005): Check isCountDown and support CountDown.
return OngoingActivityChipModel.Active.Timer(
this.key,
icon,
colors,
startTimeMs = this.promotedContent.time.elapsedRealtimeMillis,
- onClickListenerLegacy,
- clickBehavior,
+ isEventInFuture = this.promotedContent.time.isCountDown,
+ onClickListenerLegacy = onClickListenerLegacy,
+ clickBehavior = clickBehavior,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt
index 2032ec8af78c..1eb46d8cc3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.ui.binder
+import android.annotation.ElapsedRealtimeLong
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
object ChipChronometerBinder {
@@ -25,9 +26,11 @@ object ChipChronometerBinder {
* @param startTimeMs the time this event started, relative to
* [com.android.systemui.util.time.SystemClock.elapsedRealtime]. See
* [android.widget.Chronometer.setBase].
+ * @param isCountDown see [android.widget.Chronometer.setCountDown].
*/
- fun bind(startTimeMs: Long, view: ChipChronometer) {
+ fun bind(@ElapsedRealtimeLong startTimeMs: Long, isCountDown: Boolean, view: ChipChronometer) {
view.base = startTimeMs
+ view.isCountDown = isCountDown
view.start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 20dec11577df..77e0dde3dec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.ui.binder
import android.annotation.IdRes
import android.content.Context
import android.content.res.ColorStateList
+import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.view.ViewGroup
@@ -27,6 +28,7 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.UiThread
+import com.android.systemui.FontStyles
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
@@ -170,6 +172,16 @@ object OngoingActivityChipBinder {
forceLayout()
}
+ /** Updates the typefaces for any text shown in the chip. */
+ fun updateTypefaces(binding: OngoingActivityChipViewBinding) {
+ binding.timeView.typeface =
+ Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL)
+ binding.textView.typeface =
+ Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL)
+ binding.shortTimeDeltaView.typeface =
+ Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL)
+ }
+
private fun setChipIcon(
chipModel: OngoingActivityChipModel.Active,
backgroundView: ChipBackgroundContainer,
@@ -202,7 +214,7 @@ object OngoingActivityChipBinder {
)
}
is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val iconView = fetchStatusBarIconView(iconViewStore, icon)
if (iconView == null) {
// This means that the notification key doesn't exist anymore.
@@ -223,7 +235,7 @@ object OngoingActivityChipBinder {
iconViewStore: IconViewStore?,
icon: OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon,
): StatusBarIconView? {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
if (iconViewStore == null) {
throw IllegalStateException("Store should always be non-null when flag is enabled.")
}
@@ -303,7 +315,11 @@ object OngoingActivityChipBinder {
chipShortTimeDeltaView.visibility = View.GONE
}
is OngoingActivityChipModel.Active.Timer -> {
- ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
+ ChipChronometerBinder.bind(
+ chipModel.startTimeMs,
+ chipModel.isEventInFuture,
+ chipTimeView,
+ )
chipTimeView.visibility = View.VISIBLE
chipTextView.visibility = View.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 5242feac898b..fa8d25623d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.ui.compose
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -27,11 +28,13 @@ import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.constrain
import androidx.compose.ui.unit.dp
@@ -42,14 +45,16 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.rememberChronometerStat
import com.android.systemui.statusbar.chips.ui.viewmodel.rememberTimeRemainingState
import kotlin.math.min
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier = Modifier) {
val context = LocalContext.current
+ val density = LocalDensity.current
val isTextOnly = viewModel.icon == null
val hasEmbeddedIcon =
viewModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
viewModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
- val textStyle = MaterialTheme.typography.labelLarge
+ val textStyle = MaterialTheme.typography.labelLargeEmphasized
val textColor = Color(viewModel.colors.text(context))
val maxTextWidth = dimensionResource(id = R.dimen.ongoing_activity_chip_max_text_width)
val startPadding =
@@ -69,25 +74,31 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
val textMeasurer = rememberTextMeasurer()
when (viewModel) {
is OngoingActivityChipModel.Active.Timer -> {
- val timerState = rememberChronometerState(startTimeMillis = viewModel.startTimeMs)
- val text = timerState.currentTimeText
- Text(
- text = text,
- style = textStyle,
- color = textColor,
- softWrap = false,
- modifier =
- modifier
- .hideTextIfDoesNotFit(
- text = text,
- textStyle = textStyle,
- textMeasurer = textMeasurer,
- maxTextWidth = maxTextWidth,
- startPadding = startPadding,
- endPadding = endPadding,
- )
- .neverDecreaseWidth(),
- )
+ val timerState =
+ rememberChronometerState(
+ eventTimeMillis = viewModel.startTimeMs,
+ isCountDown = viewModel.isEventInFuture,
+ timeSource = viewModel.timeSource,
+ )
+ timerState.currentTimeText?.let { text ->
+ Text(
+ text = text,
+ style = textStyle,
+ color = textColor,
+ softWrap = false,
+ modifier =
+ modifier
+ .hideTextIfDoesNotFit(
+ text = text,
+ textStyle = textStyle,
+ textMeasurer = textMeasurer,
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ )
+ .neverDecreaseWidth(density),
+ )
+ }
}
is OngoingActivityChipModel.Active.Countdown -> {
@@ -97,7 +108,7 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
style = textStyle,
color = textColor,
softWrap = false,
- modifier = modifier.neverDecreaseWidth(),
+ modifier = modifier.neverDecreaseWidth(density),
)
}
@@ -150,23 +161,31 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
}
/** A modifier that ensures the width of the content only increases and never decreases. */
-private fun Modifier.neverDecreaseWidth(): Modifier {
- return this.then(NeverDecreaseWidthElement)
+private fun Modifier.neverDecreaseWidth(density: Density): Modifier {
+ return this.then(NeverDecreaseWidthElement(density))
}
-private data object NeverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() {
+private data class NeverDecreaseWidthElement(val density: Density) :
+ ModifierNodeElement<NeverDecreaseWidthNode>() {
override fun create(): NeverDecreaseWidthNode {
return NeverDecreaseWidthNode()
}
override fun update(node: NeverDecreaseWidthNode) {
- error("This should never be called")
+ node.onDensityUpdated()
}
}
private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode {
private var minWidth = 0
+ fun onDensityUpdated() {
+ // When the font or display size changes, we should re-determine what our minWidth is from
+ // scratch (e.g. if the font size decreased, we may be able to take *less* room).
+ // See b/395607413.
+ minWidth = 0
+ }
+
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 95e454ac7bda..4edb23dc9f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -18,24 +18,18 @@ package com.android.systemui.statusbar.chips.ui.compose
import android.content.res.ColorStateList
import android.view.ViewGroup
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
@@ -43,7 +37,6 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
-import com.android.compose.modifiers.thenIf
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
@@ -60,27 +53,45 @@ fun OngoingActivityChip(
iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
modifier: Modifier = Modifier,
) {
- when (val clickBehavior = model.clickBehavior) {
- is OngoingActivityChipModel.ClickBehavior.ExpandAction -> {
- // Wrap the chip in an Expandable so we can animate the expand transition.
- ExpandableChip(
- color = { Color.Transparent },
- shape =
- RoundedCornerShape(
- dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
- ),
- modifier = modifier,
- ) { expandable ->
- ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick(expandable) })
- }
+ val contentDescription =
+ when (val icon = model.icon) {
+ is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
+ is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
+ icon.contentDescription.load()
+ is OngoingActivityChipModel.ChipIcon.SingleColorIcon,
+ null -> null
}
- is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> {
- ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick() })
+
+ val borderStroke =
+ model.colors.outline(LocalContext.current)?.let {
+ BorderStroke(dimensionResource(R.dimen.ongoing_activity_chip_outline_width), Color(it))
}
- is OngoingActivityChipModel.ClickBehavior.None -> {
- ChipBody(model, iconViewStore, modifier = modifier)
+ val onClick =
+ when (val clickBehavior = model.clickBehavior) {
+ is OngoingActivityChipModel.ClickBehavior.ExpandAction -> { expandable: Expandable ->
+ clickBehavior.onClick(expandable)
+ }
+ is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { _ ->
+ clickBehavior.onClick()
+ }
+ is OngoingActivityChipModel.ClickBehavior.None -> null
}
+
+ Expandable(
+ color = Color(model.colors.background(LocalContext.current).defaultColor),
+ shape =
+ RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)),
+ modifier =
+ modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height)).semantics {
+ if (contentDescription != null) {
+ this.contentDescription = contentDescription
+ }
+ },
+ borderStroke = borderStroke,
+ onClick = onClick,
+ ) {
+ ChipBody(model, iconViewStore, isClickable = onClick != null)
}
}
@@ -88,22 +99,13 @@ fun OngoingActivityChip(
private fun ChipBody(
model: OngoingActivityChipModel.Active,
iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
+ isClickable: Boolean,
modifier: Modifier = Modifier,
- onClick: (() -> Unit)? = null,
) {
- val context = LocalContext.current
- val isClickable = onClick != null
val hasEmbeddedIcon =
model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
- val contentDescription =
- when (val icon = model.icon) {
- is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
- is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
- icon.contentDescription.load()
- is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null
- null -> null
- }
+
val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
val minWidth =
if (isClickable) {
@@ -114,68 +116,38 @@ private fun ChipBody(
dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
}
- val outline = model.colors.outline(context)
- val outlineWidth = dimensionResource(R.dimen.ongoing_activity_chip_outline_width)
-
- val shape =
- RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))
-
- // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
- // height of the chip is determined by the height of the background of the Row below.
- Box(
- contentAlignment = Alignment.Center,
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
modifier =
modifier
.fillMaxHeight()
- .clickable(enabled = isClickable, onClick = onClick ?: {})
- .semantics {
- if (contentDescription != null) {
- this.contentDescription = contentDescription
- }
- },
- ) {
- Row(
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically,
- modifier =
- Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
- .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
- .layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- layout(placeable.width, placeable.height) {
- if (constraints.maxWidth >= minWidth.roundToPx()) {
- placeable.place(0, 0)
- }
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ if (constraints.maxWidth >= minWidth.roundToPx()) {
+ placeable.place(0, 0)
}
}
- .background(Color(model.colors.background(context).defaultColor), shape = shape)
- .thenIf(outline != null) {
- Modifier.border(
- width = outlineWidth,
- color = Color(outline!!),
- shape = shape,
- )
- }
- .padding(
- horizontal =
- if (hasEmbeddedIcon) {
- dimensionResource(
- R.dimen
- .ongoing_activity_chip_side_padding_for_embedded_padding_icon
- )
- } else {
- dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
- }
- ),
- ) {
- model.icon?.let {
- ChipIcon(viewModel = it, iconViewStore = iconViewStore, colors = model.colors)
- }
+ }
+ .padding(
+ horizontal =
+ if (hasEmbeddedIcon) {
+ dimensionResource(
+ R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
+ )
+ } else {
+ dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
+ }
+ ),
+ ) {
+ model.icon?.let {
+ ChipIcon(viewModel = it, iconViewStore = iconViewStore, colors = model.colors)
+ }
- val isIconOnly = model is OngoingActivityChipModel.Active.IconOnly
- if (!isIconOnly) {
- ChipContent(viewModel = model)
- }
+ val isIconOnly = model is OngoingActivityChipModel.Active.IconOnly
+ if (!isIconOnly) {
+ ChipContent(viewModel = model)
}
}
}
@@ -195,7 +167,7 @@ private fun ChipIcon(
StatusBarIcon(colors, viewModel.impl.notification?.key, modifier) { viewModel.impl }
}
is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
check(iconViewStore != null)
StatusBarIcon(colors, viewModel.notificationKey, modifier) {
@@ -223,6 +195,7 @@ private fun StatusBarIcon(
iconFactory: () -> StatusBarIconView?,
) {
val context = LocalContext.current
+ val colorTintList = ColorStateList.valueOf(colors.text(context))
val iconSizePx =
context.resources.getDimensionPixelSize(
@@ -233,18 +206,8 @@ private fun StatusBarIcon(
factory = { _ ->
iconFactory.invoke()?.apply {
layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
- imageTintList = ColorStateList.valueOf(colors.text(context))
} ?: throw IllegalStateException("Missing StatusBarIconView for $notificationKey")
},
+ update = { iconView -> iconView.imageTintList = colorTintList },
)
}
-
-@Composable
-private fun ExpandableChip(
- color: () -> Color,
- shape: Shape,
- modifier: Modifier = Modifier,
- content: @Composable (Expandable) -> Unit,
-) {
- Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
index 3b8c0f48e40e..407849b9fae0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChips.kt
@@ -25,6 +25,7 @@ import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@@ -35,19 +36,26 @@ fun OngoingActivityChips(
iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
modifier: Modifier = Modifier,
) {
- Row(
- modifier =
- modifier
- .fillMaxHeight()
- .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement =
- Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
- ) {
- chips.active
- .filter { !it.isHidden }
- .forEach {
- key(it.key) { OngoingActivityChip(model = it, iconViewStore = iconViewStore) }
+ val shownChips = chips.active.filter { !it.isHidden }
+ if (shownChips.isNotEmpty()) {
+ Row(
+ modifier =
+ modifier
+ .fillMaxHeight()
+ .padding(start = dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement =
+ Arrangement.spacedBy(dimensionResource(R.dimen.ongoing_activity_chip_margin_start)),
+ ) {
+ shownChips.forEach {
+ key(it.key) {
+ OngoingActivityChip(
+ model = it,
+ iconViewStore = iconViewStore,
+ modifier = Modifier.sysuiResTag(it.key),
+ )
+ }
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 4954cb0a1b24..b2683762cb2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.chips.ui.model
import android.content.Context
import android.content.res.ColorStateList
import androidx.annotation.ColorInt
-import com.android.settingslib.Utils
import com.android.systemui.res.R
/** Model representing how the chip in the status bar should be colored. */
@@ -34,14 +33,14 @@ sealed interface ColorsModel {
@ColorInt fun outline(context: Context): Int?
/** The chip should match the theme's primary accent color. */
- // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it
- // only gets updated when a different configuration change happens, like a rotation.
data object AccentThemed : ColorsModel {
override fun background(context: Context): ColorStateList =
- Utils.getColorAttr(context, com.android.internal.R.attr.colorAccent)
+ ColorStateList.valueOf(
+ context.getColor(com.android.internal.R.color.materialColorPrimaryFixedDim)
+ )
override fun text(context: Context) =
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+ context.getColor(com.android.internal.R.color.materialColorOnPrimaryFixed)
override fun outline(context: Context) = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index cf0342b89c00..d37a46e58882 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.chips.ui.model
+import android.annotation.CurrentTimeMillisLong
+import android.annotation.ElapsedRealtimeLong
+import android.os.SystemClock
import android.view.View
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.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ui.viewmodel.TimeSource
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
/** Model representing the display of an ongoing activity as a chip in the status bar. */
@@ -102,7 +106,20 @@ sealed class OngoingActivityChipModel {
* [ChipChronometer] is based off of elapsed realtime. See
* [android.widget.Chronometer.setBase].
*/
- val startTimeMs: Long,
+ @ElapsedRealtimeLong val startTimeMs: Long,
+
+ /**
+ * The [TimeSource] that should be used to track the current time for this timer. Should
+ * be compatible with [startTimeMs].
+ */
+ val timeSource: TimeSource = TimeSource { SystemClock.elapsedRealtime() },
+
+ /**
+ * True if this chip represents an event starting in the future and false if this chip
+ * represents an event that has already started. If true, [startTimeMs] should be in the
+ * future. Otherwise, [startTimeMs] should be in the past.
+ */
+ val isEventInFuture: Boolean = false,
override val onClickListenerLegacy: View.OnClickListener?,
override val clickBehavior: ClickBehavior,
override val isHidden: Boolean = false,
@@ -129,10 +146,15 @@ sealed class OngoingActivityChipModel {
override val icon: ChipIcon,
override val colors: ColorsModel,
/**
- * The time of the event that this chip represents, relative to
- * [com.android.systemui.util.time.SystemClock.currentTimeMillis].
+ * The time of the event that this chip represents. Relative to
+ * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because that's what's
+ * required by [android.widget.DateTimeView].
+ *
+ * TODO(b/372657935): When the Compose chips are launched, we should convert this to be
+ * relative to [com.android.systemui.util.time.SystemClock.elapsedRealtime] so that
+ * this model and the [Timer] model use the same units.
*/
- val time: Long,
+ @CurrentTimeMillisLong val time: Long,
override val onClickListenerLegacy: View.OnClickListener?,
override val clickBehavior: ClickBehavior,
override val isHidden: Boolean = false,
@@ -148,7 +170,7 @@ sealed class OngoingActivityChipModel {
shouldAnimate,
) {
init {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
}
override val logName = "Active.ShortTimeDelta"
@@ -227,7 +249,7 @@ sealed class OngoingActivityChipModel {
val contentDescription: ContentDescription,
) : ChipIcon {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt
index 62789782d0a9..0fc7f82f785a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.statusbar.chips.ui.viewmodel
-import android.os.SystemClock
+import android.annotation.ElapsedRealtimeLong
import android.text.format.DateUtils.formatElapsedTime
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
+import kotlin.math.absoluteValue
import kotlinx.coroutines.delay
/** Platform-optimized interface for getting current time */
@@ -34,18 +35,50 @@ fun interface TimeSource {
fun getCurrentTime(): Long
}
-/** Holds and manages the state for a Chronometer */
-class ChronometerState(private val timeSource: TimeSource, private val startTimeMillis: Long) {
- private var currentTimeMillis by mutableLongStateOf(0L)
+/**
+ * Holds and manages the state for a Chronometer, which shows a timer in a format like "MM:SS" or
+ * "H:MM:SS".
+ *
+ * If [isEventInFuture] is false, then this Chronometer is counting up from an event that started in
+ * the past, like a phone call that was answered. [eventTimeMillis] represents the time the event
+ * started and the timer will tick up: 04:00, 04:01, ... No timer is shown if [eventTimeMillis] is
+ * in the future and [isEventInFuture] is false.
+ *
+ * If [isEventInFuture] is true, then this Chronometer is counting down to an event that will occur
+ * in the future, like a future meeting. [eventTimeMillis] represents the time the event will occur
+ * and the timer will tick down: 04:00, 03:59, ... No timer is shown if [eventTimeMillis] is in the
+ * past and [isEventInFuture] is true.
+ */
+class ChronometerState(
+ private val timeSource: TimeSource,
+ @ElapsedRealtimeLong private val eventTimeMillis: Long,
+ private val isEventInFuture: Boolean,
+) {
+ private var currentTimeMillis by mutableLongStateOf(timeSource.getCurrentTime())
private val elapsedTimeMillis: Long
- get() = maxOf(0L, currentTimeMillis - startTimeMillis)
+ get() =
+ if (isEventInFuture) {
+ eventTimeMillis - currentTimeMillis
+ } else {
+ currentTimeMillis - eventTimeMillis
+ }
- val currentTimeText: String by derivedStateOf { formatElapsedTime(elapsedTimeMillis / 1000) }
+ /**
+ * The current timer string in a format like "MM:SS" or "H:MM:SS", or null if we shouldn't show
+ * the timer string.
+ */
+ val currentTimeText: String? by derivedStateOf {
+ if (elapsedTimeMillis < 0) {
+ null
+ } else {
+ formatElapsedTime(elapsedTimeMillis / 1000)
+ }
+ }
suspend fun run() {
while (true) {
currentTimeMillis = timeSource.getCurrentTime()
- val delaySkewMillis = (currentTimeMillis - startTimeMillis) % 1000L
+ val delaySkewMillis = (eventTimeMillis - currentTimeMillis).absoluteValue % 1000L
delay(1000L - delaySkewMillis)
}
}
@@ -54,13 +87,16 @@ class ChronometerState(private val timeSource: TimeSource, private val startTime
/** Remember and manage the ChronometerState */
@Composable
fun rememberChronometerState(
- startTimeMillis: Long,
- timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } },
+ eventTimeMillis: Long,
+ isCountDown: Boolean,
+ timeSource: TimeSource,
): ChronometerState {
val state =
- remember(timeSource, startTimeMillis) { ChronometerState(timeSource, startTimeMillis) }
+ remember(timeSource, eventTimeMillis, isCountDown) {
+ ChronometerState(timeSource, eventTimeMillis, isCountDown)
+ }
val lifecycleOwner = LocalLifecycleOwner.current
- LaunchedEffect(lifecycleOwner, timeSource, startTimeMillis) {
+ LaunchedEffect(lifecycleOwner, timeSource, eventTimeMillis) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { state.run() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index a978c04d2a2e..229d1a56e177 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -71,7 +71,7 @@ interface OngoingActivityChipViewModel {
tag: String,
): (Expandable) -> Unit {
return { expandable ->
- StatusBarChipsModernization.assertInNewMode()
+ StatusBarChipsModernization.unsafeAssertInNewMode()
logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 1a30caf0150b..8228b5533fca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -350,6 +350,26 @@ constructor(
.stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModelLegacy())
}
+ private val activeChips =
+ if (StatusBarChipsModernization.isEnabled) {
+ chips.map { it.active }
+ } else {
+ chipsLegacy.map {
+ val list = mutableListOf<OngoingActivityChipModel.Active>()
+ if (it.primary is OngoingActivityChipModel.Active) {
+ list.add(it.primary)
+ }
+ if (it.secondary is OngoingActivityChipModel.Active) {
+ list.add(it.secondary)
+ }
+ list
+ }
+ }
+
+ /** A flow modeling just the keys for the currently visible chips. */
+ val visibleChipKeys: Flow<List<String>> =
+ activeChips.map { chips -> chips.filter { !it.isHidden }.map { it.key } }
+
/**
* Sort the given chip [bundle] in order of priority, and divide the chips between active,
* overflow, and inactive (see [MultipleOngoingActivityChipsModel] for a description of each).
@@ -535,7 +555,6 @@ constructor(
secondary = DEFAULT_INTERNAL_INACTIVE_MODEL,
)
- // TODO(b/392886257): Support 3 chips if there's space available.
- private const val MAX_VISIBLE_CHIPS = 2
+ private const val MAX_VISIBLE_CHIPS = 3
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt
index eb6ebcaa5796..803d422c0f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
-import android.os.SystemClock
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
@@ -39,7 +38,14 @@ import kotlinx.coroutines.delay
* Manages state and updates for the duration remaining between now and a given time in the future.
*/
class TimeRemainingState(private val timeSource: TimeSource, private val futureTimeMillis: Long) {
- private var durationRemaining by mutableStateOf(Duration.ZERO)
+ // Start with the right duration from the outset so we don't use "now" as an initial value.
+ private var durationRemaining by
+ mutableStateOf(
+ calculateDurationRemaining(
+ currentTimeMillis = timeSource.getCurrentTime(),
+ futureTimeMillis = futureTimeMillis,
+ )
+ )
private var startTimeMillis: Long = 0
/**
@@ -56,7 +62,11 @@ class TimeRemainingState(private val timeSource: TimeSource, private val futureT
while (true) {
val currentTime = timeSource.getCurrentTime()
durationRemaining =
- (futureTimeMillis - currentTime).toDuration(DurationUnit.MILLISECONDS)
+ calculateDurationRemaining(
+ currentTimeMillis = currentTime,
+ futureTimeMillis = futureTimeMillis,
+ )
+
// No need to update if duration is more than 1 minute in the past. Because, we will
// stop displaying anything.
if (durationRemaining.inWholeMilliseconds < -1.minutes.inWholeMilliseconds) {
@@ -67,6 +77,13 @@ class TimeRemainingState(private val timeSource: TimeSource, private val futureT
}
}
+ private fun calculateDurationRemaining(
+ currentTimeMillis: Long,
+ futureTimeMillis: Long,
+ ): Duration {
+ return (futureTimeMillis - currentTimeMillis).toDuration(DurationUnit.MILLISECONDS)
+ }
+
private fun calculateNextUpdateDelay(duration: Duration): Long {
val durationAbsolute = duration.absoluteValue
return when {
@@ -85,7 +102,7 @@ class TimeRemainingState(private val timeSource: TimeSource, private val futureT
@Composable
fun rememberTimeRemainingState(
futureTimeMillis: Long,
- timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } },
+ timeSource: TimeSource = remember { TimeSource { System.currentTimeMillis() } },
): TimeRemainingState {
val state =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
index d25ca285c53b..601d105dfa8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -47,7 +47,7 @@ constructor(
) : CoreStartable {
override fun start() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val resultPerDisplay: Map<String, RegisterStatusBarResult> =
try {
barService.registerStatusBarForAllDisplays(commandQueue)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
index 70697913447f..7964950a2917 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
@@ -16,25 +16,19 @@
package com.android.systemui.statusbar.core
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import com.android.systemui.statusbar.phone.AutoHideControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
-import dagger.Lazy
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-/** [PerDisplayStoreImpl] for providing display specific [StatusBarOrchestrator]. */
+/** [StatusBarPerDisplayStoreImpl] for providing display specific [StatusBarOrchestrator]. */
@SysUISingleton
class MultiDisplayStatusBarOrchestratorStore
@Inject
@@ -48,10 +42,14 @@ constructor(
private val autoHideControllerStore: AutoHideControllerStore,
private val displayScopeRepository: DisplayScopeRepository,
private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
-) : PerDisplayStoreImpl<StatusBarOrchestrator>(backgroundApplicationScope, displayRepository) {
+) :
+ StatusBarPerDisplayStoreImpl<StatusBarOrchestrator>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarOrchestrator? {
@@ -79,21 +77,3 @@ constructor(
instance.stop()
}
}
-
-@Module
-interface MultiDisplayStatusBarOrchestratorStoreModule {
-
- @Provides
- @SysUISingleton
- @IntoMap
- @ClassKey(MultiDisplayStatusBarOrchestratorStore::class)
- fun storeAsCoreStartable(
- multiDisplayLazy: Lazy<MultiDisplayStatusBarOrchestratorStore>
- ): CoreStartable {
- return if (StatusBarConnectedDisplays.isEnabled) {
- multiDisplayLazy.get()
- } else {
- CoreStartable.NOP
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index dfb3763fd3f6..30c4ca5269d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.core
import android.view.Display
-import android.view.IWindowManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -44,28 +43,21 @@ constructor(
private val statusBarInitializerStore: StatusBarInitializerStore,
private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
private val lightBarControllerStore: LightBarControllerStore,
- private val windowManager: IWindowManager,
) : CoreStartable {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun start() {
applicationScope.launch {
- displayRepository.displays
+ displayRepository.displayIdsWithSystemDecorations
.pairwiseBy { previousDisplays, currentDisplays ->
currentDisplays - previousDisplays
}
- .onStart { emit(displayRepository.displays.value) }
+ .onStart { emit(displayRepository.displayIdsWithSystemDecorations.value) }
.collect { newDisplays ->
- newDisplays.forEach {
- // TODO(b/393191204): Split navbar, status bar, etc. functionality
- // from WindowManager#shouldShowSystemDecors.
- if (windowManager.shouldShowSystemDecors(it.displayId)) {
- createAndStartComponentsForDisplay(it.displayId)
- }
- }
+ newDisplays.forEach { createAndStartComponentsForDisplay(it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
index 402881d438dc..03316d3e326c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
@@ -49,7 +49,9 @@ object NewStatusBarIcons {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
index 06474b01287d..a58e00c5ee0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
@@ -50,7 +50,9 @@ object StatusBarConnectedDisplays {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index de6cd072afd7..48955cc4ab21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -20,11 +20,11 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -45,10 +45,13 @@ constructor(
private val darkIconDispatcherStore: DarkIconDispatcherStore,
) :
StatusBarInitializerStore,
- PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<StatusBarInitializer>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index 73ada546a9fc..c4c25c21a3b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -146,7 +146,7 @@ constructor(
/** Starts status bar orchestration. To be called when status bar is created. */
fun start() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
startJob =
coroutineScope
// Perform animations on the main thread to prevent crashes.
@@ -280,7 +280,7 @@ constructor(
* Called when the [StatusBarOrchestrator] should stop doing any work and clean up if needed.
*/
fun stop() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
dumpManager.unregisterDumpable(dumpableName)
startJob?.cancel()
startJob = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
index 3c30f3cbec85..25dace32261e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
@@ -52,7 +52,9 @@ object StatusBarRootModernization {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
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 434eb7d3d410..a7929ecbd801 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -33,6 +33,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
@@ -44,7 +45,6 @@ import com.android.systemui.shade.ShadeSurfaceImpl;
import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index 27d815190d85..9db2f4b46dae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -22,6 +22,7 @@ import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModul
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayConfigurationStateModule
import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule
import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
import dagger.Module
@@ -34,6 +35,7 @@ import dagger.Module
LightBarControllerStoreModule::class,
RemoteInputRepositoryModule::class,
StatusBarConfigurationControllerModule::class,
+ StatusBarPerDisplayConfigurationStateModule::class,
StatusBarContentInsetsProviderStoreModule::class,
StatusBarModeRepositoryModule::class,
StatusBarPhoneDataLayerModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
index 041f3c816149..f353c0198b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
@@ -24,7 +24,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -59,10 +58,13 @@ constructor(
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
) :
SysuiDarkIconDispatcherStore,
- PerDisplayStoreImpl<SysuiDarkIconDispatcher>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<SysuiDarkIconDispatcher>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): SysuiDarkIconDispatcher? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
index c629d10b90b0..3c0d6c3b8df3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.LightBarControllerImpl
import dagger.Binds
@@ -47,7 +46,10 @@ constructor(
private val darkIconDispatcherStore: DarkIconDispatcherStore,
) :
LightBarControllerStore,
- PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<LightBarController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
override fun createInstanceForDisplay(displayId: Int): LightBarController? {
val darkIconDispatcher = darkIconDispatcherStore.forDisplay(displayId) ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
index d48c94bd0893..32dc8407ac90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.PrivacyDotViewController
@@ -50,7 +49,10 @@ constructor(
private val contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
PrivacyDotViewControllerStore,
- PerDisplayStoreImpl<PrivacyDotViewController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<PrivacyDotViewController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
override fun createInstanceForDisplay(displayId: Int): PrivacyDotViewController? {
val configurationController =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
index 86c3c483165c..7fc5e8abe904 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.PrivacyDotWindowController
import dagger.Binds
@@ -52,10 +51,13 @@ constructor(
private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
) :
PrivacyDotWindowControllerStore,
- PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<PrivacyDotWindowController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
index 38cea832ad76..36b11ac60827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
@@ -24,7 +24,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -53,13 +52,13 @@ constructor(
private val configurationControllerFactory: ConfigurationControllerImpl.Factory,
) :
StatusBarConfigurationControllerStore,
- PerDisplayStoreImpl<StatusBarConfigurationController>(
+ StatusBarPerDisplayStoreImpl<StatusBarConfigurationController>(
backgroundApplicationScope,
displayRepository,
) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarConfigurationController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
index 5ea12110b00c..3cd4b5c885de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
@@ -54,7 +53,7 @@ constructor(
private val cameraProtectionLoaderFactory: CameraProtectionLoaderImpl.Factory,
) :
StatusBarContentInsetsProviderStore,
- PerDisplayStoreImpl<StatusBarContentInsetsProvider>(
+ StatusBarPerDisplayStoreImpl<StatusBarContentInsetsProvider>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 7fa9f0e9dcc2..a658e1d55f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -267,7 +267,8 @@ constructor(
if (StatusBarChipsModernization.isEnabled) {
ongoingProcessRequiresStatusBarVisible
} else {
- ongoingCallStateLegacy is OngoingCallModel.InCall
+ ongoingCallStateLegacy is OngoingCallModel.InCall &&
+ !ongoingCallStateLegacy.isAppVisible
}
val statusBarMode =
toBarMode(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 143e99823860..5980c9c57214 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
@@ -48,13 +47,13 @@ constructor(
displayRepository: DisplayRepository,
) :
StatusBarModeRepositoryStore,
- PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+ StatusBarPerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
backgroundApplicationScope,
displayRepository,
) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
new file mode 100644
index 000000000000..3168a22c56ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayInstanceProvider
+import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.display.data.repository.SingleInstanceRepositoryImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import javax.inject.Inject
+
+@SysUISingleton
+class StatusBarPerDisplayConfigurationStateProvider
+@Inject
+constructor(
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore,
+ private val factory: ConfigurationStateImpl.Factory,
+) : PerDisplayInstanceProvider<ConfigurationState> {
+
+ override fun createInstance(displayId: Int): ConfigurationState? {
+ val displayWindowProperties =
+ displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null
+ val configController =
+ statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null
+ return factory.create(displayWindowProperties.context, configController)
+ }
+}
+
+@Module
+object StatusBarPerDisplayConfigurationStateModule {
+
+ @Provides
+ @SysUISingleton
+ fun store(
+ instanceProvider: Lazy<StatusBarPerDisplayConfigurationStateProvider>,
+ factory: PerDisplayInstanceRepositoryImpl.Factory<ConfigurationState>,
+ defaultInstance: ConfigurationState,
+ ): PerDisplayRepository<ConfigurationState> {
+ val name = "ConfigurationState"
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ factory.create(name, instanceProvider.get())
+ } else {
+ SingleInstanceRepositoryImpl(name, defaultInstance)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt
new file mode 100644
index 000000000000..e873c02fd4d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.CoroutineScope
+
+/** [PerDisplayStoreImpl] for Status Bar related classes. */
+abstract class StatusBarPerDisplayStoreImpl<T>(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+) : PerDisplayStoreImpl<T>(backgroundApplicationScope, displayRepository) {
+
+ override fun start() {
+ val instanceType = instanceClass.simpleName
+ backgroundApplicationScope.launch("StatusBarPerDisplayStore#<$instanceType>start") {
+ displayRepository.displayIdsWithSystemDecorations
+ .pairwiseBy { previousDisplays, currentDisplays ->
+ previousDisplays - currentDisplays
+ }
+ .collect { removedDisplayIds ->
+ removedDisplayIds.forEach { removedDisplayId ->
+ val removedInstance = perDisplayInstances.remove(removedDisplayId)
+ removedInstance?.let { onDisplayRemovalAction(it) }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
index ffc125539521..7b9ea697a9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -23,7 +23,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.SystemEventChipAnimationController
import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
@@ -53,13 +52,13 @@ constructor(
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
SystemEventChipAnimationControllerStore,
- PerDisplayStoreImpl<SystemEventChipAnimationController>(
+ StatusBarPerDisplayStoreImpl<SystemEventChipAnimationController>(
backgroundApplicationScope,
displayRepository,
) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
index aeeb0427d24b..e91e9777d48e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
@@ -14,7 +14,7 @@
package com.android.systemui.statusbar.disableflags.data.repository
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
index 4b9721ea4fe5..b881c920c34b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
@@ -37,7 +37,7 @@ constructor(
) : SystemEventChipAnimationController {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun prepareChipAnimation(viewCreator: ViewCreator) {
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 63410d7465b8..3200ca18b0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -190,7 +190,7 @@ constructor(
}
override fun stop() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
contentInsetsProvider.removeCallback(insetsChangedListener)
configurationController.removeCallback(configurationListener)
stateController.removeCallback(statusBarStateListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
index a90e3ff4b2dc..05d32567c4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
@@ -54,7 +54,7 @@ constructor(level: Int, context: Context, attrs: AttributeSet? = null) :
get() = composeInner
init {
- NewStatusBarIcons.assertInNewMode()
+ NewStatusBarIcons.unsafeAssertInNewMode()
inflate(context, R.layout.status_bar_event_chip_compose, this)
roundedContainer = requireViewById(R.id.rounded_container)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
index 90f97df295f5..ec6508717e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
@@ -17,51 +17,51 @@
package com.android.systemui.statusbar.featurepods.media.ui.viewmodel
import android.content.Context
+import androidx.compose.runtime.getValue
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
import com.android.systemui.statusbar.featurepods.popups.shared.model.HoverBehavior
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/**
* [StatusBarPopupChipViewModel] for a media control chip in the status bar. This view model is
* responsible for converting the [MediaControlChipModel] to a [PopupChipModel] that can be used to
* display a media control chip.
*/
-@SysUISingleton
class MediaControlChipViewModel
-@Inject
+@AssistedInject
constructor(
- @Background private val backgroundScope: CoroutineScope,
@Application private val applicationContext: Context,
mediaControlChipInteractor: MediaControlChipInteractor,
-) : StatusBarPopupChipViewModel {
-
+) : StatusBarPopupChipViewModel, ExclusiveActivatable() {
+ private val hydrator: Hydrator = Hydrator("MediaControlChipViewModel.hydrator")
/**
- * A [StateFlow] of the current [PopupChipModel]. This flow emits a new [PopupChipModel]
+ * A snapshot [State] of the current [PopupChipModel]. This emits a new [PopupChipModel]
* whenever the underlying [MediaControlChipModel] changes.
*/
- override val chip: StateFlow<PopupChipModel> =
- mediaControlChipInteractor.mediaControlChipModel
- .map { mediaControlChipModel -> toPopupChipModel(mediaControlChipModel) }
- .stateIn(
- backgroundScope,
- SharingStarted.WhileSubscribed(),
- PopupChipModel.Hidden(PopupChipId.MediaControl),
- )
+ override val chip: PopupChipModel by
+ hydrator.hydratedStateOf(
+ traceName = "chip",
+ initialValue = PopupChipModel.Hidden(PopupChipId.MediaControl),
+ source =
+ mediaControlChipInteractor.mediaControlChipModel.map { model ->
+ toPopupChipModel(model)
+ },
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
private fun toPopupChipModel(model: MediaControlChipModel?): PopupChipModel {
if (model == null || model.songName.isNullOrEmpty()) {
@@ -96,7 +96,12 @@ constructor(
return HoverBehavior.Button(
icon = Icon.Loaded(drawable = icon, contentDescription = contentDescription),
- onIconPressed = { backgroundScope.launch { action.run() } },
+ onIconPressed = { action.run() },
)
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): MediaControlChipViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
index a9f72ff21a7c..d1307aa0ee31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
@@ -50,7 +50,9 @@ object StatusBarPopupChips {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
index 5712be30ccd6..38f24137d355 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
@@ -16,14 +16,14 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
+import com.android.systemui.lifecycle.Activatable
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
-import kotlinx.coroutines.flow.StateFlow
/**
* Interface for a view model that knows the display requirements for a single type of status bar
* popup chip.
*/
-interface StatusBarPopupChipViewModel {
- /** A flow modeling the popup chip that should be shown (or not shown). */
- val chip: StateFlow<PopupChipModel>
+interface StatusBarPopupChipViewModel : Activatable {
+ /** A snapshot [State] modeling the popup chip that should be shown (or not shown). */
+ val chip: PopupChipModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
index 33bf90defb48..35f1a9981691 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
@@ -21,7 +21,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.MediaControlChipViewModel
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
@@ -36,18 +35,17 @@ import kotlinx.coroutines.flow.map
*/
class StatusBarPopupChipsViewModel
@AssistedInject
-constructor(mediaControlChip: MediaControlChipViewModel) : ExclusiveActivatable() {
- private val hydrator: Hydrator = Hydrator("StatusBarPopupChipsViewModel.hydrator")
+constructor(mediaControlChipFactory: MediaControlChipViewModel.Factory) : ExclusiveActivatable() {
+
+ private val mediaControlChip by lazy { mediaControlChipFactory.create() }
/** The ID of the current chip that is showing its popup, or `null` if no chip is shown. */
private var currentShownPopupChipId by mutableStateOf<PopupChipId?>(null)
- private val incomingPopupChipBundle: PopupChipBundle by
- hydrator.hydratedStateOf(
- traceName = "incomingPopupChipBundle",
- initialValue = PopupChipBundle(),
- source = mediaControlChip.chip.map { chip -> PopupChipBundle(media = chip) },
- )
+ private val incomingPopupChipBundle: PopupChipBundle by derivedStateOf {
+ val mediaChip = mediaControlChip.chip
+ PopupChipBundle(media = mediaChip)
+ }
val shownPopupChips: List<PopupChipModel.Shown> by derivedStateOf {
if (StatusBarPopupChips.isEnabled) {
@@ -66,7 +64,7 @@ constructor(mediaControlChip: MediaControlChipViewModel) : ExclusiveActivatable(
}
override suspend fun onActivated(): Nothing {
- hydrator.activate()
+ mediaControlChip.activate()
}
private data class PopupChipBundle(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
index c9024d934068..7b1024ca65dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
@@ -50,7 +50,9 @@ object StatusBarNoHunBehavior {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
index 9d5d87f6db7c..e588dd303346 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
@@ -201,7 +201,7 @@ constructor(
}
override fun stop() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
configurationController.removeCallback(this)
dumpManager.unregisterDumpable(dumpableName)
commandRegistry.unregisterCommand(commandName)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
index d2dccc49ffd7..d7e9bb2ad64a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
@@ -21,10 +21,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import dagger.Lazy
import dagger.Module
import dagger.Provides
@@ -45,7 +45,7 @@ constructor(
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
StatusBarContentInsetsViewModelStore,
- PerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
+ StatusBarPerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 4053d065cb16..8be9e410f8f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -124,8 +124,14 @@ public final class NotificationClicker implements View.OnClickListener {
Notification notification = sbn.getNotification();
if (notification.contentIntent != null || notification.fullScreenIntent != null
|| row.getEntry().isBubble()) {
- row.setBubbleClickListener(v ->
- mNotificationActivityStarter.onNotificationBubbleIconClicked(row.getEntry()));
+ if (NotificationBundleUi.isEnabled()) {
+ row.setBubbleClickListener(
+ v -> row.getEntryAdapter().onNotificationBubbleIconClicked());
+ } else {
+ row.setBubbleClickListener(v ->
+ mNotificationActivityStarter.onNotificationBubbleIconClicked(
+ row.getEntry()));
+ }
row.setOnClickListener(this);
row.setOnDragSuccessListener(mOnDragSuccessListener);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
index 0a24d7a71ce9..68c13afe82dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
@@ -88,8 +88,8 @@ class PhysicsPropertyAnimator {
@JvmStatic
fun createDefaultSpring(): SpringForce {
return SpringForce()
- .setStiffness(380f) // MEDIUM LOW STIFFNESS
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) // LOW BOUNCINESS
+ .setStiffness(380f)
+ .setDampingRatio(0.68f);
}
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 6dd44a123538..f6535730cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -33,7 +33,8 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import kotlinx.coroutines.flow.MutableStateFlow;
@@ -45,34 +46,37 @@ import kotlinx.coroutines.flow.StateFlowKt;
*/
public class BundleEntry extends PipelineEntry {
- private final BundleEntryAdapter mEntryAdapter;
-
// TODO(b/394483200): move NotificationEntry's implementation to PipelineEntry?
private final MutableStateFlow<Boolean> mSensitive = StateFlowKt.MutableStateFlow(false);
// TODO (b/389839319): implement the row
private ExpandableNotificationRow mRow;
+ private final List<ListEntry> mChildren = new ArrayList<>();
+
+ private final List<ListEntry> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
+
public BundleEntry(String key) {
super(key);
- mEntryAdapter = new BundleEntryAdapter();
}
- @Nullable
- @Override
- public NotificationEntry getRepresentativeEntry() {
- return null;
+ void addChild(ListEntry child) {
+ mChildren.add(child);
}
- @Nullable
- @Override
- public NotifSection getSection() {
- return null;
+ @NonNull
+ public List<ListEntry> getChildren() {
+ return mUnmodifiableChildren;
}
+ /**
+ * @return Null because bundles do not have an associated NotificationEntry.
+ */
+
+ @Nullable
@Override
- public int getSectionIndex() {
- return 0;
+ public NotificationEntry getRepresentativeEntry() {
+ return null;
}
@Nullable
@@ -86,126 +90,9 @@ public class BundleEntry extends PipelineEntry {
return false;
}
- @VisibleForTesting
- public BundleEntryAdapter getEntryAdapter() {
- return mEntryAdapter;
- }
-
- public class BundleEntryAdapter implements EntryAdapter {
-
- /**
- * TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated?
- */
- @Override
- public GroupEntry getParent() {
- return GroupEntry.ROOT_ENTRY;
- }
-
- @Override
- public boolean isTopLevelEntry() {
- return true;
- }
-
- @NonNull
- @Override
- public String getKey() {
- return mKey;
- }
-
- @Override
- @Nullable
- public ExpandableNotificationRow getRow() {
- return mRow;
- }
-
- @Override
- public boolean isGroupRoot() {
- return true;
- }
-
- @Override
- public StateFlow<Boolean> isSensitive() {
- return BundleEntry.this.mSensitive;
- }
-
- @Override
- public boolean isClearable() {
- // TODO(b/394483200): check whether all of the children are clearable, when implemented
- return true;
- }
-
- @Override
- public int getTargetSdk() {
- return Build.VERSION_CODES.CUR_DEVELOPMENT;
- }
-
- @Override
- public String getSummarization() {
- return null;
- }
-
- @Override
- public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
- return Notification.COLOR_DEFAULT;
- }
-
- @Override
- public boolean canPeek() {
- return false;
- }
-
- @Override
- public long getWhen() {
- return 0;
- }
-
- @Override
- public IconPack getIcons() {
- // TODO(b/396446620): implement bundle icons
- return null;
- }
-
- @Override
- public boolean isColorized() {
- return false;
- }
-
- @Override
- @Nullable
- public StatusBarNotification getSbn() {
- return null;
- }
-
- @Override
- public boolean canDragAndDrop() {
- return false;
- }
-
- @Override
- public boolean isBubbleCapable() {
- return false;
- }
-
- @Override
- @Nullable
- public String getStyle() {
- return null;
- }
-
- @Override
- public int getSectionBucket() {
- return mBucket;
- }
-
- @Override
- public boolean isAmbient() {
- return false;
- }
-
- @Override
- public boolean isFullScreenCapable() {
- return false;
- }
+ @Nullable
+ public ExpandableNotificationRow getRow() {
+ return mRow;
}
public static final List<BundleEntry> ROOT_BUNDLES = List.of(
@@ -213,4 +100,8 @@ public class BundleEntry extends PipelineEntry {
new BundleEntry(SOCIAL_MEDIA_ID),
new BundleEntry(NEWS_ID),
new BundleEntry(RECS_ID));
+
+ public MutableStateFlow<Boolean> isSensitive() {
+ return mSensitive;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
new file mode 100644
index 000000000000..b1a26af336d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.app.Notification
+import android.content.Context
+import android.os.Build
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import kotlinx.coroutines.flow.StateFlow
+
+class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter {
+ /** TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated? */
+ override fun getParent(): GroupEntry {
+ return GroupEntry.ROOT_ENTRY
+ }
+
+ override fun isTopLevelEntry(): Boolean {
+ return true
+ }
+
+ override fun getKey(): String {
+ return entry.key
+ }
+
+ override fun getRow(): ExpandableNotificationRow? {
+ return entry.row
+ }
+
+ override fun isGroupRoot(): Boolean {
+ return true
+ }
+
+ override fun isSensitive(): StateFlow<Boolean> {
+ return entry.isSensitive
+ }
+
+ override fun isClearable(): Boolean {
+ // TODO(b/394483200): check whether all of the children are clearable, when implemented
+ return true
+ }
+
+ override fun getTargetSdk(): Int {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT
+ }
+
+ override fun getSummarization(): String? {
+ return null
+ }
+
+ override fun getContrastedColor(
+ context: Context?,
+ isLowPriority: Boolean,
+ backgroundColor: Int,
+ ): Int {
+ return Notification.COLOR_DEFAULT
+ }
+
+ override fun canPeek(): Boolean {
+ return false
+ }
+
+ override fun getWhen(): Long {
+ return 0
+ }
+
+ override fun getIcons(): IconPack? {
+ // TODO(b/396446620): implement bundle icons
+ return null
+ }
+
+ override fun isColorized(): Boolean {
+ return false
+ }
+
+ override fun getSbn(): StatusBarNotification? {
+ return null
+ }
+
+ override fun canDragAndDrop(): Boolean {
+ return false
+ }
+
+ override fun isBubbleCapable(): Boolean {
+ return false
+ }
+
+ override fun getStyle(): String? {
+ return null
+ }
+
+ override fun getSectionBucket(): Int {
+ return entry.bucket
+ }
+
+ override fun isAmbient(): Boolean {
+ return false
+ }
+
+ override fun isPromotedOngoing(): Boolean {
+ return false
+ }
+
+ override fun isFullScreenCapable(): Boolean {
+ return false
+ }
+
+ override fun onNotificationBubbleIconClicked() {
+ // do nothing. these cannot be a bubble
+ Log.wtf(TAG, "onNotificationBubbleIconClicked() called")
+ }
+
+ override fun onNotificationActionClicked() {
+ // do nothing. these have no actions
+ Log.wtf(TAG, "onNotificationActionClicked() called")
+ }
+}
+
+private const val TAG = "BundleEntryAdapter"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 307a9573d5d8..4299825bd5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -50,7 +50,7 @@ public interface EntryAdapter {
/**
* Gets the view that this entry is backing.
*/
- @NonNull
+ @Nullable
ExpandableNotificationRow getRow();
/**
@@ -132,8 +132,23 @@ public interface EntryAdapter {
boolean isAmbient();
+ /**
+ * Returns whether this row represents promoted ongoing notification.
+ */
+ boolean isPromotedOngoing();
+
default boolean isFullScreenCapable() {
return false;
}
+
+ /**
+ * Process a click on a notification bubble icon
+ */
+ void onNotificationBubbleIconClicked();
+
+ /**
+ * Processes a click on a notification action
+ */
+ void onNotificationActionClicked();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactory.kt
new file mode 100644
index 000000000000..b7a84ddef103
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactory.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+/** Creates an appropriate EntryAdapter for the entry type given */
+interface EntryAdapterFactory {
+ fun create(entry: PipelineEntry): EntryAdapter
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt
new file mode 100644
index 000000000000..a5169865c3c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.NotificationActionClickManager
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
+import javax.inject.Inject
+
+/** Creates an appropriate EntryAdapter for the entry type given */
+class EntryAdapterFactoryImpl
+@Inject
+constructor(
+ private val notificationActivityStarter: NotificationActivityStarter,
+ private val metricsLogger: MetricsLogger,
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ private val iconStyleProvider: NotificationIconStyleProvider,
+ private val visualStabilityCoordinator: VisualStabilityCoordinator,
+ private val notificationActionClickManager: NotificationActionClickManager,
+) : EntryAdapterFactory {
+ override fun create(entry: PipelineEntry): EntryAdapter {
+ return if (entry is NotificationEntry) {
+ NotificationEntryAdapter(
+ notificationActivityStarter,
+ metricsLogger,
+ peopleNotificationIdentifier,
+ iconStyleProvider,
+ visualStabilityCoordinator,
+ notificationActionClickManager,
+ entry,
+ )
+ } else {
+ BundleEntryAdapter((entry as BundleEntry))
+ }
+ }
+}
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 4a1b9568c714..04dc7d5ed3ff 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
@@ -31,8 +31,8 @@ data class ListAttachState private constructor(
var parent: PipelineEntry?,
/**
- * The section that this ListEntry was sorted into. If the child of the group, this will be the
- * parent's section. Null if not attached to the list.
+ * The section that this PipelineEntry was sorted into. If the child of the group, this will be
+ * the parent's section. Null if not attached to the list.
*/
var section: NotifSection?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 697d0a06cf9d..caa7abb1aa7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -66,10 +66,6 @@ public abstract class ListEntry extends PipelineEntry {
return mPreviousAttachState.getParent();
}
- public int getSectionIndex() {
- return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1;
- }
-
/**
* Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
* fresh attach state (all entries will be null/default-initialized).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index f662a040fae6..0fc0e9c5eab8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -24,6 +24,7 @@ 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.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
@@ -169,6 +170,14 @@ class NotifPipeline @Inject constructor(
}
/**
+ * NotifBundler that is used to determine whether a notification should be bundled according to
+ * classification.
+ */
+ fun setNotifBundler(bundler: NotifBundler) {
+ mShadeListBuilder.setBundler(bundler)
+ }
+
+ /**
* StabilityManager that is used to determine whether to suppress group and section changes.
* This should only be set once.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index b19ba3a18826..d031d831bf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -29,8 +29,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static com.android.systemui.statusbar.notification.collection.BundleEntry.ROOT_BUNDLES;
-import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static java.util.Objects.requireNonNull;
@@ -41,7 +39,6 @@ import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
-import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
@@ -68,7 +65,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
@@ -81,14 +77,14 @@ import com.android.systemui.statusbar.notification.row.shared.NotificationRowCon
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.ListenerSet;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
/**
* Represents a notification that the system UI knows about
*
@@ -109,7 +105,6 @@ public final class NotificationEntry extends ListEntry {
private final String mKey;
private StatusBarNotification mSbn;
private Ranking mRanking;
- private final NotifEntryAdapter mEntryAdapter;
/*
* Bookkeeping members
@@ -270,141 +265,6 @@ public final class NotificationEntry extends ListEntry {
mKey = sbn.getKey();
setSbn(sbn);
setRanking(ranking);
- mEntryAdapter = new NotifEntryAdapter();
- }
-
- public class NotifEntryAdapter implements EntryAdapter {
- @Override
- public PipelineEntry getParent() {
- return NotificationEntry.this.getParent();
- }
-
- @Override
- public boolean isTopLevelEntry() {
- return getParent() != null
- && (getParent() == ROOT_ENTRY || ROOT_BUNDLES.contains(getParent()));
- }
-
- @Override
- public String getKey() {
- return NotificationEntry.this.getKey();
- }
-
- @Override
- public ExpandableNotificationRow getRow() {
- return NotificationEntry.this.getRow();
- }
-
- @Override
- public boolean isGroupRoot() {
- if (isTopLevelEntry() || getParent() == null) {
- return false;
- }
- if (NotificationEntry.this.getParent() instanceof GroupEntry parentGroupEntry) {
- return parentGroupEntry.getSummary() == NotificationEntry.this;
- }
- return false;
- }
-
- @Override
- public StateFlow<Boolean> isSensitive() {
- return NotificationEntry.this.isSensitive();
- }
-
- @Override
- public boolean isClearable() {
- return NotificationEntry.this.isClearable();
- }
-
- @Override
- public int getTargetSdk() {
- return NotificationEntry.this.targetSdk;
- }
-
- @Override
- public String getSummarization() {
- return getRanking().getSummarization();
- }
-
- @Override
- public void prepareForInflation() {
- getSbn().clearPackageContext();
- }
-
- @Override
- public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
- return NotificationEntry.this.getContrastedColor(
- context, isLowPriority, backgroundColor);
- }
-
- @Override
- public boolean canPeek() {
- return isStickyAndNotDemoted();
- }
-
- @Override
- public long getWhen() {
- return getSbn().getNotification().getWhen();
- }
-
- @Override
- public IconPack getIcons() {
- return NotificationEntry.this.getIcons();
- }
-
- @Override
- public boolean isColorized() {
- return getSbn().getNotification().isColorized();
- }
-
- @Override
- @Nullable
- public StatusBarNotification getSbn() {
- return NotificationEntry.this.getSbn();
- }
-
- @Override
- public boolean canDragAndDrop() {
- boolean canBubble = canBubble();
- Notification notif = getSbn().getNotification();
- PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent
- : notif.fullScreenIntent;
- if (dragIntent != null && dragIntent.isActivity() && !canBubble) {
- return true;
- }
- return false;
- }
-
- @Override
- public boolean isBubbleCapable() {
- return NotificationEntry.this.isBubble();
- }
-
- @Override
- @Nullable
- public String getStyle() {
- return getNotificationStyle();
- }
-
- @Override
- public int getSectionBucket() {
- return mBucket;
- }
-
- @Override
- public boolean isAmbient() {
- return mRanking.isAmbient();
- }
-
- @Override
- public boolean isFullScreenCapable() {
- return getSbn().getNotification().fullScreenIntent != null;
- }
-
- }
-
- public EntryAdapter getEntryAdapter() {
- return mEntryAdapter;
}
@Override
@@ -633,7 +493,8 @@ public final class NotificationEntry extends ListEntry {
public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
if (NotificationBundleUi.isEnabled()) {
if (isGroupSummary()) {
- return ((GroupEntry) getParent()).getChildren();
+ GroupEntry parent = (GroupEntry) getParent();
+ return parent != null ? new ArrayList<>(parent.getChildren()) : null;
}
} else {
if (row == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
new file mode 100644
index 000000000000..345b6aae9673
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.content.Context
+import android.service.notification.StatusBarNotification
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator
+import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationActionClickManager
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
+import kotlinx.coroutines.flow.StateFlow
+
+class NotificationEntryAdapter(
+ private val notificationActivityStarter: NotificationActivityStarter,
+ private val metricsLogger: MetricsLogger,
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ private val iconStyleProvider: NotificationIconStyleProvider,
+ private val visualStabilityCoordinator: VisualStabilityCoordinator,
+ private val notificationActionClickManager: NotificationActionClickManager,
+ private val entry: NotificationEntry,
+) : EntryAdapter {
+
+ override fun getParent(): PipelineEntry? {
+ return entry.parent
+ }
+
+ override fun isTopLevelEntry(): Boolean {
+ return parent != null &&
+ (parent === GroupEntry.ROOT_ENTRY || BundleEntry.ROOT_BUNDLES.contains(parent))
+ }
+
+ override fun getKey(): String {
+ return entry.key
+ }
+
+ override fun getRow(): ExpandableNotificationRow {
+ return entry.row
+ }
+
+ override fun isGroupRoot(): Boolean {
+ if (isTopLevelEntry || parent == null) {
+ return false
+ }
+ return (entry.parent as? GroupEntry)?.summary == entry
+ }
+
+ override fun isSensitive(): StateFlow<Boolean> {
+ return entry.isSensitive
+ }
+
+ override fun isClearable(): Boolean {
+ return entry.isClearable
+ }
+
+ override fun getTargetSdk(): Int {
+ return entry.targetSdk
+ }
+
+ override fun getSummarization(): String? {
+ return entry.ranking?.summarization
+ }
+
+ override fun prepareForInflation() {
+ entry.sbn.clearPackageContext()
+ }
+
+ override fun getContrastedColor(
+ context: Context?,
+ isLowPriority: Boolean,
+ backgroundColor: Int,
+ ): Int {
+ return entry.getContrastedColor(context, isLowPriority, backgroundColor)
+ }
+
+ override fun canPeek(): Boolean {
+ return entry.isStickyAndNotDemoted
+ }
+
+ override fun getWhen(): Long {
+ return entry.sbn.notification.getWhen()
+ }
+
+ override fun getIcons(): IconPack {
+ return entry.icons
+ }
+
+ override fun isColorized(): Boolean {
+ return entry.sbn.notification.isColorized
+ }
+
+ override fun getSbn(): StatusBarNotification {
+ return entry.sbn
+ }
+
+ override fun canDragAndDrop(): Boolean {
+ val canBubble: Boolean = entry.canBubble()
+ val notif = entry.sbn.notification
+ val dragIntent =
+ if (notif.contentIntent != null) notif.contentIntent else notif.fullScreenIntent
+ if (dragIntent != null && dragIntent.isActivity && !canBubble) {
+ return true
+ }
+ return false
+ }
+
+ override fun isBubbleCapable(): Boolean {
+ return entry.isBubble
+ }
+
+ override fun getStyle(): String? {
+ return entry.notificationStyle
+ }
+
+ override fun getSectionBucket(): Int {
+ return entry.bucket
+ }
+
+ override fun isAmbient(): Boolean {
+ return entry.ranking.isAmbient
+ }
+
+ override fun isPromotedOngoing(): Boolean {
+ return entry.isPromotedOngoing
+ }
+
+ override fun isFullScreenCapable(): Boolean {
+ return entry.sbn.notification.fullScreenIntent != null
+ }
+
+ override fun onNotificationBubbleIconClicked() {
+ notificationActivityStarter.onNotificationBubbleIconClicked(entry)
+ }
+
+ override fun onNotificationActionClicked() {
+ notificationActionClickManager.onNotificationActionClicked(entry)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
index 84de77bac352..872cd68e1b21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
@@ -70,7 +70,9 @@ public abstract class PipelineEntry {
/**
* @return Index of section assigned to this entry.
*/
- public abstract int getSectionIndex();
+ public int getSectionIndex() {
+ return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1;
+ }
/**
* @return Parent PipelineEntry
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 bb84ab8f421a..780e8f47a7fe 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
@@ -48,6 +48,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.collection.coordinator.BundleCoordinator;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -58,8 +59,10 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.SemiSt
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.DefaultNotifBundler;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -78,6 +81,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -122,7 +126,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
private final List<NotifSection> mNotifSections = new ArrayList<>();
private NotifStabilityManager mNotifStabilityManager;
-
+ private NotifBundler mNotifBundler;
+ private Map<String, BundleEntry> mIdToBundleEntry = new HashMap<>();
private final NamedListenerSet<OnBeforeTransformGroupsListener>
mOnBeforeTransformGroupsListeners = new NamedListenerSet<>();
private final NamedListenerSet<OnBeforeSortListener>
@@ -273,6 +278,24 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
}
}
+ void setBundler(NotifBundler bundler) {
+ Assert.isMainThread();
+ mPipelineState.requireState(STATE_IDLE);
+
+ mNotifBundler = bundler;
+ if (mNotifBundler == null) {
+ throw new IllegalStateException(TAG + ".setBundler: null");
+ }
+
+ mIdToBundleEntry.clear();
+ for (String id: mNotifBundler.getBundleIds()) {
+ if (BundleCoordinator.debugBundleUi) {
+ Log.i(TAG, "create BundleEntry with id: " + id);
+ }
+ mIdToBundleEntry.put(id, new BundleEntry(id));
+ }
+ }
+
void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -297,6 +320,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
return mNotifStabilityManager;
}
+ @NonNull
+ private NotifBundler getNotifBundler() {
+ if (mNotifBundler == null) {
+ return DefaultNotifBundler.INSTANCE;
+ }
+ return mNotifBundler;
+ }
+
void setComparators(List<NotifComparator> comparators) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -651,7 +682,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
j--;
}
}
- } else {
+ } else if (tle instanceof NotificationEntry) {
// maybe put top-level-entries back into their previous groups
if (maybeSuppressGroupChange(tle.getRepresentativeEntry(), topLevelList)) {
// entry was put back into its previous group, so we remove it from the list of
@@ -659,7 +690,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
topLevelList.remove(i);
i--;
}
- }
+ } // Promoters ignore bundles so we don't have to demote any here.
}
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
index e6d5f4120a20..4478d0e97920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
@@ -20,15 +20,19 @@ import android.app.NotificationChannel.NEWS_ID
import android.app.NotificationChannel.PROMOTIONS_ID
import android.app.NotificationChannel.RECS_ID
import android.app.NotificationChannel.SOCIAL_MEDIA_ID
-import com.android.systemui.statusbar.notification.collection.PipelineEntry
+import android.app.NotificationChannel.SYSTEM_RESERVED_IDS
import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.NewsHeader
import com.android.systemui.statusbar.notification.dagger.PromoHeader
import com.android.systemui.statusbar.notification.dagger.RecsHeader
import com.android.systemui.statusbar.notification.dagger.SocialHeader
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.BUCKET_NEWS
import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO
import com.android.systemui.statusbar.notification.stack.BUCKET_RECS
@@ -90,6 +94,30 @@ class BundleCoordinator @Inject constructor(
}
}
+ val bundler =
+ object : NotifBundler("NotifBundler") {
+
+ // Use list instead of set to keep fixed order
+ override val bundleIds: List<String> =
+ if (debugBundleUi) SYSTEM_RESERVED_IDS + "notify"
+ else SYSTEM_RESERVED_IDS
+
+ override fun getBundleIdOrNull(entry: NotificationEntry?): String? {
+ if (debugBundleUi && entry?.key?.contains("notify") == true) {
+ return "notify"
+ }
+ return entry?.representativeEntry?.channel?.id?.takeIf { it in this.bundleIds }
+ }
+ }
+
override fun attach(pipeline: NotifPipeline) {
+ if (NotificationBundleUi.isEnabled) {
+ pipeline.setNotifBundler(bundler)
+ }
+ }
+
+ companion object {
+ @kotlin.jvm.JvmField
+ var debugBundleUi: Boolean = true
}
}
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 611e0ef77fac..a0eab43f854b 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
@@ -26,11 +26,12 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.BundleEntry
import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
@@ -47,7 +48,9 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.row.NotificationActionClickManager
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
@@ -82,6 +85,7 @@ constructor(
private val mHeadsUpViewBinder: HeadsUpViewBinder,
private val mVisualInterruptionDecisionProvider: VisualInterruptionDecisionProvider,
private val mRemoteInputManager: NotificationRemoteInputManager,
+ private val notificationActionClickManager: NotificationActionClickManager,
private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
private val mFlags: NotifPipelineFlags,
private val statusBarNotificationChipsInteractor: StatusBarNotificationChipsInteractor,
@@ -107,7 +111,11 @@ constructor(
pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
pipeline.addPromoter(mNotifPromoter)
pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
- mRemoteInputManager.addActionPressListener(mActionPressListener)
+ if (NotificationBundleUi.isEnabled) {
+ notificationActionClickManager.addActionClickListener(mActionPressListener)
+ } else {
+ mRemoteInputManager.addActionPressListener(mActionPressListener)
+ }
if (StatusBarNotifChips.isEnabled) {
applicationScope.launch {
@@ -125,7 +133,7 @@ constructor(
* Must be run on the main thread.
*/
private fun onPromotedNotificationChipTapEvent(key: String) {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
val entry = notifCollection.getEntry(key)
if (entry == null) {
@@ -423,6 +431,7 @@ constructor(
map[child.key] = GroupLocation.Child
}
}
+ is BundleEntry -> map[topLevelEntry.key] = GroupLocation.Bundle
else -> error("unhandled type $topLevelEntry")
}
}
@@ -459,7 +468,12 @@ constructor(
} else {
if (posted.isHeadsUpEntry) {
// We don't want this to be interrupting anymore, let's remove it
- hunMutator.removeNotification(posted.key, false /*removeImmediately*/)
+ // If the notification is pinned by the user, the only way a user can un-pin
+ // it is by tapping the status bar notification chip. Since that's a clear
+ // user action, we should remove the HUN immediately instead of waiting for
+ // any sort of minimum timeout.
+ val shouldRemoveImmediately = posted.isPinnedByUser
+ hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
} else {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
@@ -776,7 +790,7 @@ constructor(
*/
private val mActionPressListener =
Consumer<NotificationEntry> { entry ->
- mHeadsUpManager.setUserActionMayIndirectlyRemove(entry)
+ mHeadsUpManager.setUserActionMayIndirectlyRemove(entry.key)
mExecutor.execute { endNotifLifetimeExtensionIfExtended(entry) }
}
@@ -945,6 +959,7 @@ private enum class GroupLocation {
Isolated,
Summary,
Child,
+ Bundle,
}
private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index 56deb18df9ab..d542e67e665a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -26,6 +26,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.BundleEntry
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -193,6 +194,7 @@ constructor(
when (it) {
is NotificationEntry -> listOfNotNull(it)
is GroupEntry -> it.children
+ is BundleEntry -> emptyList()
else -> error("unhandled type of $it")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index df0cde51e8ba..818ef6b335c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
@@ -113,11 +114,12 @@ constructor(
mCoordinators.add(remoteInputCoordinator)
mCoordinators.add(dismissibilityCoordinator)
mCoordinators.add(automaticPromotionCoordinator)
-
+ if (NotificationBundleUi.isEnabled) {
+ mCoordinators.add(bundleCoordinator)
+ }
if (NotificationsLiveDataStoreRefactor.isEnabled) {
mCoordinators.add(statsLoggerCoordinator)
}
-
// Manually add Ordered Sections
if (NotificationMinimalism.isEnabled) {
mOrderedSections.add(lockScreenMinimalismCoordinator.topOngoingSectioner) // Top Ongoing
@@ -135,7 +137,7 @@ constructor(
mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
}
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
- if (NotificationClassificationFlag.isEnabled) {
+ if (NotificationClassificationFlag.isEnabled && !NotificationBundleUi.isEnabled) {
mOrderedSections.add(bundleCoordinator.newsSectioner)
mOrderedSections.add(bundleCoordinator.socialSectioner)
mOrderedSections.add(bundleCoordinator.recsSectioner)
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 20c6736b74c8..b54f21b23bba 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
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.notification.collection.BundleEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -54,6 +55,7 @@ import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -306,7 +308,9 @@ public class PreparationCoordinator implements Coordinator {
private void inflateAllRequiredViews(List<PipelineEntry> entries) {
for (int i = 0, size = entries.size(); i < size; i++) {
PipelineEntry entry = entries.get(i);
- if (entry instanceof GroupEntry) {
+ if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry) {
+ // TODO(b/399738511) Inflate bundle views.
+ } else if (entry instanceof GroupEntry) {
GroupEntry groupEntry = (GroupEntry) entry;
inflateRequiredGroupViews(groupEntry);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index d1063d95a305..cda535de86c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.BundleEntry;
import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -98,7 +101,11 @@ public class RankingCoordinator implements Coordinator {
NotificationPriorityBucketKt.BUCKET_ALERTING) {
@Override
public boolean isInSection(PipelineEntry entry) {
- return mHighPriorityProvider.isHighPriority(entry);
+ return mHighPriorityProvider.isHighPriority(entry)
+ && entry.getRepresentativeEntry() != null
+ && entry.getRepresentativeEntry().getChannel() != null
+ && !SYSTEM_RESERVED_IDS.contains(
+ entry.getRepresentativeEntry().getChannel().getId());
}
@Nullable
@@ -116,6 +123,9 @@ public class RankingCoordinator implements Coordinator {
NotificationPriorityBucketKt.BUCKET_SILENT) {
@Override
public boolean isInSection(PipelineEntry entry) {
+ if (entry instanceof BundleEntry) {
+ return true;
+ }
return !mHighPriorityProvider.isHighPriority(entry)
&& entry.getRepresentativeEntry() != null
&& !entry.getRepresentativeEntry().isAmbient();
@@ -132,7 +142,12 @@ public class RankingCoordinator implements Coordinator {
public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
mHasSilentEntries = false;
for (int i = 0; i < entries.size(); i++) {
- if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
+ NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry();
+ if (notifEntry == null) {
+ // TODO(b/395698521) Handle BundleEntry
+ continue;
+ }
+ if (notifEntry.getSbn().isClearable()) {
mHasSilentEntries = true;
break;
}
@@ -147,6 +162,7 @@ public class RankingCoordinator implements Coordinator {
@Override
public boolean isInSection(PipelineEntry entry) {
return !mHighPriorityProvider.isHighPriority(entry)
+ && entry.getRepresentativeEntry() != null
&& entry.getRepresentativeEntry().isAmbient();
}
@@ -161,7 +177,12 @@ public class RankingCoordinator implements Coordinator {
public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
mHasMinimizedEntries = false;
for (int i = 0; i < entries.size(); i++) {
- if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
+ NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry();
+ if (notifEntry == null) {
+ // TODO(b/395698521) Handle BundleEntry
+ continue;
+ }
+ if (notifEntry.getSbn().isClearable()) {
mHasMinimizedEntries = true;
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index e7c767f42c12..27c0dcccfe43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -221,20 +221,20 @@ constructor(
mSmartReplyHistoryExtender.isExtending(key)
} else false
- override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) {
- if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})")
+ override fun releaseNotificationIfKeptForRemoteInputHistory(entryKey: String) {
+ if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entryKey})")
if (!lifetimeExtensionRefactor()) {
mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(
- entry.key,
+ entryKey,
REMOTE_INPUT_EXTENDER_RELEASE_DELAY,
)
mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(
- entry.key,
+ entryKey,
REMOTE_INPUT_EXTENDER_RELEASE_DELAY,
)
}
mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(
- entry.key,
+ entryKey,
REMOTE_INPUT_EXTENDER_RELEASE_DELAY,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
index a0e44bfd7620..cf7881214ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
@@ -50,7 +50,9 @@ object StabilizeHeadsUpGroup {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index a0a86710b4ba..f43767d3effb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -22,7 +22,6 @@ import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Flags
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
@@ -147,9 +146,7 @@ internal constructor(
traceSection("updateNotifOnUiModeChanged") {
mPipeline?.allNotifs?.forEach { entry ->
entry.row?.onUiModeChanged()
- if (Flags.notificationUndoGutsOnConfigChanged()) {
- mGutsManager.closeAndUndoGuts()
- }
+ mGutsManager.closeAndUndoGuts()
}
}
}
@@ -158,16 +155,7 @@ internal constructor(
colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()")
mPipeline?.allNotifs?.forEach { entry ->
entry.onDensityOrFontScaleChanged()
- if (Flags.notificationUndoGutsOnConfigChanged()) {
- mGutsManager.closeAndUndoGuts()
- } else {
- // This property actually gets reset when the guts are re-inflated, so we're never
- // actually calling onDensityOrFontScaleChanged below.
- val exposedGuts = entry.areGutsExposed()
- if (exposedGuts) {
- mGutsManager.onDensityOrFontScaleChanged(entry)
- }
- }
+ mGutsManager.closeAndUndoGuts()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index bdbdc53c4b1c..27765635edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -39,9 +39,9 @@ import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
@@ -113,6 +113,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
@VisibleForTesting
protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500;
+ private final boolean mCheckLockScreenTransitionEnabled = Flags.checkLockscreenGoneTransition();
+
@Inject
public VisualStabilityCoordinator(
@Background DelayableExecutor delayableExecutor,
@@ -182,7 +184,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
this::onTrackingHeadsUpModeChanged);
}
- if (Flags.checkLockscreenGoneTransition()) {
+ if (mCheckLockScreenTransitionEnabled) {
if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone), null),
@@ -437,7 +439,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
boolean wasReorderingAllowed = mReorderingAllowed;
// No need to run notification pipeline when the lockscreen is in fading animation.
mPipelineRunAllowed = !(isPanelCollapsingOrLaunchingActivity()
- || (Flags.checkLockscreenGoneTransition() && mLockscreenInGoneTransition));
+ || (mCheckLockScreenTransitionEnabled && mLockscreenInGoneTransition));
mReorderingAllowed = isReorderingAllowed();
if (wasPipelineRunAllowed != mPipelineRunAllowed
|| wasReorderingAllowed != mReorderingAllowed) {
@@ -566,7 +568,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
pw.println("pipelineRunAllowed: " + mPipelineRunAllowed);
pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing);
pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity);
- if (Flags.checkLockscreenGoneTransition()) {
+ if (mCheckLockScreenTransitionEnabled) {
pw.println(" lockscreenInGoneTransition: " + mLockscreenInGoneTransition);
}
pw.println("reorderingAllowed: " + mReorderingAllowed);
@@ -627,7 +629,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
}
private void onLockscreenInGoneTransitionChanged(boolean inGoneTransition) {
- if (!Flags.checkLockscreenGoneTransition()) {
+ if (!mCheckLockScreenTransitionEnabled) {
return;
}
if (inGoneTransition == mLockscreenInGoneTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index c2f0806a9cd6..6b32c6a18ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection.inflation;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -276,7 +275,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
if (inflaterParams.isChildInGroup()
- && redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ && redactionType != REDACTION_TYPE_NONE) {
params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
} else {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt
new file mode 100644
index 000000000000..14a9113f7eb4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pluggable
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/** Pluggable for bundling notifications according to classification. */
+abstract class NotifBundler protected constructor(name: String?) : Pluggable<NotifBundler?>(name) {
+ abstract val bundleIds: List<String>
+
+ abstract fun getBundleIdOrNull(entry: NotificationEntry?): String?
+}
+
+/** The default, no-op instance of NotifBundler which does not bundle anything. */
+object DefaultNotifBundler : NotifBundler("DefaultNotifBundler") {
+ override val bundleIds: List<String>
+ get() = listOf()
+
+ override fun getBundleIdOrNull(entry: NotificationEntry?): String? {
+ return null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index c5ae8756ded6..0d4207bd95a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -162,7 +162,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public boolean isGroupExpanded(EntryAdapter entry) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
ExpandableNotificationRow parent = entry.getRow().getNotificationParent();
return mExpandedCollections.contains(entry)
|| (parent != null && mExpandedCollections.contains(parent.getEntryAdapter()));
@@ -170,7 +170,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public void setGroupExpanded(EntryAdapter groupRoot, boolean expanded) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
if (!groupRoot.isAttached()) {
if (expanded) {
Log.wtf(TAG, "Cannot expand group that is not attached");
@@ -192,7 +192,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public boolean toggleGroupExpansion(EntryAdapter groupRoot) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
setGroupExpanded(groupRoot, !isGroupExpanded(groupRoot));
return isGroupExpanded(groupRoot);
}
@@ -231,7 +231,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
}
private void sendOnGroupExpandedChange(EntryAdapter entry, boolean expanded) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
for (OnGroupExpansionChangeListener listener : mOnGroupChangeListeners) {
listener.onGroupExpansionChange(entry.getRow(), expanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index aec0d70e4a88..66a4f9fe3f80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -59,7 +59,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager {
@Override
public boolean isGroupRoot(@NonNull EntryAdapter entry) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
return entry.isGroupRoot();
}
@@ -85,7 +85,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager {
@Override
public boolean isChildInGroup(@NonNull EntryAdapter entry) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
// An entry is a child if it's not a group root or top level entry, but it is attached.
return entry.isAttached() && !entry.isGroupRoot() && !entry.isTopLevelEntry();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 8021d8f58ccc..a552ca554fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -234,21 +234,21 @@ private class ShadeNode(val controller: NodeController) {
fun getChildCount(): Int = controller.getChildCount()
fun addChildAt(child: ShadeNode, index: Int) {
- traceSection("ShadeNode#addChildAt") {
+ traceSection({ "ShadeNode#${controller::class.simpleName}#addChildAt" }) {
controller.addChildAt(child.controller, index)
child.controller.onViewAdded()
}
}
fun moveChildTo(child: ShadeNode, index: Int) {
- traceSection("ShadeNode#moveChildTo") {
+ traceSection({ "ShadeNode#${controller::class.simpleName}#moveChildTo" }) {
controller.moveChildTo(child.controller, index)
child.controller.onViewMoved()
}
}
fun removeChild(child: ShadeNode, isTransfer: Boolean) {
- traceSection("ShadeNode#removeChild") {
+ traceSection({ "ShadeNode#${controller::class.simpleName}#removeChild" }) {
controller.removeChild(child.controller, isTransfer)
child.controller.onViewRemoved()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 53d5dbc58677..1e5aa01714be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -37,6 +37,8 @@ import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory;
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -81,6 +83,7 @@ import com.android.systemui.statusbar.notification.logging.dagger.NotificationsL
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.row.NotificationActionClickManager;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -340,4 +343,9 @@ public interface NotificationsModule {
return MagneticNotificationRowManager.getEmpty();
}
}
+
+ /** Provides an instance of {@link EntryAdapterFactory} */
+ @Binds
+ EntryAdapterFactory provideEntryAdapterFactory(EntryAdapterFactoryImpl impl);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
index f1fc2751d11f..52bf894c796c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
@@ -50,7 +50,9 @@ object ModesEmptyShadeFix {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
index 7e6c6050d0b4..9bb9b9aedc3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -124,7 +124,7 @@ constructor(
}
val footer: FooterMessageViewModel by lazy {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
FooterMessageViewModel(
messageId = R.string.unlock_to_see_notif_text,
iconId = R.drawable.ic_friction_lock_closed,
@@ -133,7 +133,7 @@ constructor(
}
val onClick: Flow<SettingsIntent> by lazy {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
combine(
zenModeInteractor.modesHidingNotifications,
notificationSettingsInteractor.isNotificationHistoryEnabled,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt
index 0307d90aaf4c..90faa4d2c714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt
@@ -50,7 +50,9 @@ object NotifRedesignFooter {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index 8eca16622084..c401d8212c29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.headsup
import android.os.Handler
-import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
@@ -24,9 +23,9 @@ import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.util.Compile
import java.io.PrintWriter
import javax.inject.Inject
@@ -155,6 +154,7 @@ constructor(
} else if (entry in nextMap) {
outcome = "update next"
nextMap[entry]?.add(runnable)
+ checkNextPinnedByUser(entry)?.let { outcome = "$outcome & $it" }
} else if (headsUpEntryShowing == null) {
outcome = "show now"
showNow(entry, arrayListOf(runnable))
@@ -166,17 +166,22 @@ constructor(
outcome = "add next"
addToNext(entry, runnable)
- // Shorten headsUpEntryShowing display time
- val nextIndex = nextList.indexOf(entry)
- val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
- if (isOnlyNextEntry) {
- // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
- // and goes to the isShowing case above
- headsUpEntryShowing!!.updateEntry(
- /* updatePostTime= */ false,
- /* updateEarliestRemovalTime= */ false,
- /* reason= */ "shorten duration of previously-last HUN",
- )
+ val nextIsPinnedByUserResult = checkNextPinnedByUser(entry)
+ if (nextIsPinnedByUserResult != null) {
+ outcome = "$outcome & $nextIsPinnedByUserResult"
+ } else {
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ "shorten duration of previously-last HUN",
+ )
+ }
}
}
outcome += getStateStr()
@@ -190,6 +195,28 @@ constructor(
}
/**
+ * Checks if the given entry is requesting [PinnedStatus.PinnedByUser] status and makes the
+ * correct updates if needed.
+ *
+ * @return a string representing the outcome, or null if nothing changed.
+ */
+ private fun checkNextPinnedByUser(entry: HeadsUpEntry): String? {
+ if (
+ StatusBarNotifChips.isEnabled &&
+ entry.requestedPinnedStatus == PinnedStatus.PinnedByUser
+ ) {
+ val string = "next is PinnedByUser"
+ headsUpEntryShowing?.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ string,
+ )
+ return string
+ }
+ return null
+ }
+
+ /**
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
@@ -243,19 +270,22 @@ constructor(
outcome = "remove showing. ${getStateStr()}"
} else {
runnable.run()
- outcome = "run runnable for untracked HUN " +
+ outcome =
+ "run runnable for untracked HUN " +
"(was dropped or shown when AC was disabled). ${getStateStr()}"
}
headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
- * Returns duration based on
+ * Returns how much longer the given entry should show based on:
* 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
- * 2) The priority of the top HUN in the next batch Used by
- * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
+ * 2) The priority of the top HUN in the next batch
+ *
+ * Used by [HeadsUpManagerImpl.HeadsUpEntry]'s finishTimeCalculator to shorten display duration.
*/
- fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
+ fun getDuration(entry: HeadsUpEntry?, autoDismissMsValue: Int): RemainingDuration {
+ val autoDismissMs = RemainingDuration.UpdatedDuration(autoDismissMsValue)
if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
@@ -273,7 +303,11 @@ constructor(
val thisKey = getKey(entry)
if (entryList.isEmpty()) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "No avalanche HUNs, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "No avalanche HUNs, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
// entryList.indexOf(entry) returns -1 even when the entry is in entryList
@@ -285,28 +319,64 @@ constructor(
}
if (thisEntryIndex == -1) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "Untracked entry, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "Untracked entry, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
val nextEntryIndex = thisEntryIndex + 1
if (nextEntryIndex >= entryList.size) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "Last entry, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "Last entry, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
val nextEntry = entryList[nextEntryIndex]
val nextKey = getKey(nextEntry)
+
+ if (
+ StatusBarNotifChips.isEnabled &&
+ nextEntry.requestedPinnedStatus == PinnedStatus.PinnedByUser
+ ) {
+ return RemainingDuration.HideImmediately.also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "next is PinnedByUser",
+ nextKey,
+ )
+ }
+ }
if (nextEntry.compareNonTimeFields(entry) == -1) {
- headsUpManagerLogger.logAvalancheDuration(
- thisKey, 500, "LOWER priority than next: ", nextKey)
- return 500
+ return RemainingDuration.UpdatedDuration(500).also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "LOWER priority than next: ",
+ nextKey,
+ )
+ }
} else if (nextEntry.compareNonTimeFields(entry) == 0) {
- headsUpManagerLogger.logAvalancheDuration(
- thisKey, 1000, "SAME priority as next: ", nextKey)
- return 1000
+ return RemainingDuration.UpdatedDuration(1000).also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "SAME priority as next: ",
+ nextKey,
+ )
+ }
} else {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "HIGHER priority than next: ", nextKey)
+ thisKey,
+ autoDismissMs,
+ "HIGHER priority than next: ",
+ nextKey,
+ )
return autoDismissMs
}
}
@@ -377,11 +447,11 @@ constructor(
}
private fun showNext() {
- headsUpManagerLogger.logAvalancheStage("show next", key = "")
+ headsUpManagerLogger.logAvalancheStage("show next", key = "")
headsUpEntryShowing = null
if (nextList.isEmpty()) {
- headsUpManagerLogger.logAvalancheStage("no more", key = "")
+ headsUpManagerLogger.logAvalancheStage("no more", key = "")
previousHunKey = ""
return
}
@@ -432,10 +502,12 @@ constructor(
private fun getStateStr(): String {
return "\n[AC state]" +
- "\nshow: ${getKey(headsUpEntryShowing)}" +
- "\nprevious: $previousHunKey" +
- "\n$nextStr" +
- "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " + baseEntryMapStr() + "\n"
+ "\nshow: ${getKey(headsUpEntryShowing)}" +
+ "\nprevious: $previousHunKey" +
+ "\n$nextStr" +
+ "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " +
+ baseEntryMapStr() +
+ "\n"
}
private val nextStr: String
@@ -447,7 +519,7 @@ constructor(
// This should never happen
val nextMapStr = nextMap.keys.joinToString("\n ") { getKey(it) }
return "next list (${nextList.size}):\n $nextListStr" +
- "\nnext map (${nextMap.size}):\n $nextMapStr"
+ "\nnext map (${nextMap.size}):\n $nextMapStr"
}
fun getKey(entry: HeadsUpEntry?): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt
new file mode 100644
index 000000000000..ab8489653f50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.headsup
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/** Models the data needed for a heads-up notification animation. */
+data class HeadsUpAnimationEvent(
+ /** The row corresponding to the heads-up notification. */
+ val row: ExpandableNotificationRow,
+ /**
+ * True if this notification should do a appearance animation, false if this notification should
+ * do a disappear animation.
+ */
+ val isHeadsUpAppearance: Boolean,
+ /** True if the status bar is showing a chip corresponding to this notification. */
+ val hasStatusBarChip: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
index f9bd805a2628..2f2d80a46f60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
@@ -17,21 +17,27 @@
package com.android.systemui.statusbar.notification.headsup
import android.content.Context
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.res.R
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
/**
* A class shared between [StackScrollAlgorithm] and [StackStateAnimator] to ensure all heads up
* animations use the same animation values.
+ *
+ * @param systemBarUtilsProxy optional utility class to provide the status bar height. Typically
+ * null in production code and non-null in tests.
*/
-class HeadsUpAnimator(context: Context) {
+class HeadsUpAnimator(context: Context, private val systemBarUtilsProxy: SystemBarUtilsProxy?) {
init {
- NotificationsHunSharedAnimationValues.assertInNewMode()
+ NotificationsHunSharedAnimationValues.unsafeAssertInNewMode()
}
var headsUpAppearHeightBottom: Int = 0
var stackTopMargin: Int = 0
private var headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ private var statusBarHeight = fetchStatusBarHeight(context)
/**
* Returns the Y translation for a heads-up notification animation.
@@ -40,14 +46,20 @@ class HeadsUpAnimator(context: Context) {
* animation. For a disappear animation, the returned Y translation should be the ending value
* of the animation.
*/
- fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean): Int {
- NotificationsHunSharedAnimationValues.assertInNewMode()
+ fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean, hasStatusBarChip: Boolean): Int {
+ if (NotificationsHunSharedAnimationValues.isUnexpectedlyInLegacyMode()) return 0
if (isHeadsUpFromBottom) {
// start from or end at the bottom of the screen
return headsUpAppearHeightBottom + headsUpAppearStartAboveScreen
}
+ if (hasStatusBarChip) {
+ // If this notification is also represented by a chip in the status bar, we don't want
+ // any HUN transitions to obscure that chip.
+ return statusBarHeight - stackTopMargin
+ }
+
// start from or end at the top of the screen
return -stackTopMargin - headsUpAppearStartAboveScreen
}
@@ -55,9 +67,15 @@ class HeadsUpAnimator(context: Context) {
/** Should be invoked when resource values may have changed. */
fun updateResources(context: Context) {
headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ statusBarHeight = fetchStatusBarHeight(context)
}
private fun Context.fetchHeadsUpAppearStartAboveScreen(): Int {
return this.resources.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen)
}
+
+ private fun fetchStatusBarHeight(context: Context): Int {
+ return systemBarUtilsProxy?.getStatusBarHeight()
+ ?: SystemBarUtils.getStatusBarHeight(context)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
index 9728fcfcd6ba..25ae50c34659 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.headsup
import android.graphics.Region
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import dagger.Binds
@@ -155,9 +154,9 @@ interface HeadsUpManager : Dumpable {
fun setAnimationStateHandler(handler: AnimationStateHandler)
/**
- * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until
- * it's collapsed again.
- */
+ * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until
+ * it's collapsed again.
+ */
fun setExpanded(key: String, row: ExpandableNotificationRow, expanded: Boolean)
/**
@@ -199,12 +198,12 @@ interface HeadsUpManager : Dumpable {
* Notes that the user took an action on an entry that might indirectly cause the system or the
* app to remove the notification.
*
- * @param entry the entry that might be indirectly removed by the user's action
+ * @param entry the key of the entry that might be indirectly removed by the user's action
* @see
* com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator.mActionPressListener
* @see .canRemoveImmediately
*/
- fun setUserActionMayIndirectlyRemove(entry: NotificationEntry)
+ fun setUserActionMayIndirectlyRemove(entryKey: String)
/**
* Decides whether a click is invalid for a notification, i.e. it has not been shown long enough
@@ -332,7 +331,7 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
override fun setUser(user: Int) {}
- override fun setUserActionMayIndirectlyRemove(entry: NotificationEntry) {}
+ override fun setUserActionMayIndirectlyRemove(entryKey: String) {}
override fun shouldSwallowClick(key: String): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
index 6525b6f1186b..376735025abd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.headsup
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 5157e7a5e42f..ca83666079ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -46,7 +46,6 @@ import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
-import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -320,15 +319,17 @@ public class HeadsUpManagerImpl
mLogger.logShowNotificationRequest(entry, isPinnedByUser);
+ PinnedStatus requestedPinnedStatus =
+ isPinnedByUser
+ ? PinnedStatus.PinnedByUser
+ : PinnedStatus.PinnedBySystem;
+ headsUpEntry.setRequestedPinnedStatus(requestedPinnedStatus);
+
Runnable runnable = () -> {
mLogger.logShowNotification(entry, isPinnedByUser);
// Add new entry and begin managing it
mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- PinnedStatus requestedPinnedStatus =
- isPinnedByUser
- ? PinnedStatus.PinnedByUser
- : PinnedStatus.PinnedBySystem;
onEntryAdded(headsUpEntry, requestedPinnedStatus);
// TODO(b/328390331) move accessibility events to the view layer
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -1096,7 +1097,7 @@ public class HeadsUpManagerImpl
@Override
public void setExpanded(@NonNull String entryKey, @NonNull ExpandableNotificationRow row,
boolean expanded) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entryKey);
if (headsUpEntry != null && row.getPinnedStatus().isPinned()) {
headsUpEntry.setExpanded(expanded);
@@ -1125,8 +1126,8 @@ public class HeadsUpManagerImpl
* @see HeadsUpCoordinator.mActionPressListener
* @see #canRemoveImmediately(String)
*/
- public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+ public void setUserActionMayIndirectlyRemove(@NonNull String entryKey) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(entryKey);
if (headsUpEntry != null) {
headsUpEntry.mUserActionMayIndirectlyRemove = true;
}
@@ -1289,10 +1290,17 @@ public class HeadsUpManagerImpl
@Nullable private Runnable mCancelRemoveRunnable;
private boolean mGutsShownPinned;
+ /** The *current* pinned status of this HUN. */
private final MutableStateFlow<PinnedStatus> mPinnedStatus =
StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned);
/**
+ * The *requested* pinned status of this HUN. {@link AvalancheController} uses this value to
+ * know if the current HUN needs to be removed so that a pinned-by-user HUN can show.
+ */
+ private PinnedStatus mRequestedPinnedStatus = PinnedStatus.NotPinned;
+
+ /**
* If the time this entry has been on was extended
*/
private boolean extended;
@@ -1352,6 +1360,20 @@ public class HeadsUpManagerImpl
}
}
+ /** Sets what pinned status this HUN is requesting. */
+ void setRequestedPinnedStatus(PinnedStatus pinnedStatus) {
+ if (!StatusBarNotifChips.isEnabled() && pinnedStatus == PinnedStatus.PinnedByUser) {
+ Log.w(TAG, "PinnedByUser status not allowed if StatusBarNotifChips is disabled");
+ mRequestedPinnedStatus = PinnedStatus.NotPinned;
+ } else {
+ mRequestedPinnedStatus = pinnedStatus;
+ }
+ }
+
+ PinnedStatus getRequestedPinnedStatus() {
+ return mRequestedPinnedStatus;
+ }
+
@VisibleForTesting
void setRowPinnedStatus(PinnedStatus pinnedStatus) {
if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus);
@@ -1410,11 +1432,29 @@ public class HeadsUpManagerImpl
}
FinishTimeUpdater finishTimeCalculator = () -> {
- final long finishTime = calculateFinishTime();
+ RemainingDuration remainingDuration =
+ mAvalancheController.getDuration(this, mAutoDismissTime);
+
+ if (remainingDuration instanceof RemainingDuration.HideImmediately) {
+ /* Check if */ StatusBarNotifChips.isUnexpectedlyInLegacyMode();
+ return 0;
+ }
+
+ int remainingTimeoutMs;
+ if (isStickyForSomeTime()) {
+ remainingTimeoutMs = mStickyForSomeTimeAutoDismissTime;
+ } else {
+ remainingTimeoutMs =
+ ((RemainingDuration.UpdatedDuration) remainingDuration).getDuration();
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(remainingTimeoutMs);
+ final long timeoutTimestamp =
+ mPostTime + duration + (extended ? mExtensionTime : 0);
+
final long now = mSystemClock.elapsedRealtime();
return NotificationThrottleHun.isEnabled()
- ? Math.max(finishTime, mEarliestRemovalTime) - now
- : Math.max(finishTime - now, mMinimumDisplayTimeDefault);
+ ? Math.max(timeoutTimestamp, mEarliestRemovalTime) - now
+ : Math.max(timeoutTimestamp - now, mMinimumDisplayTimeDefault);
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -1696,21 +1736,6 @@ public class HeadsUpManagerImpl
}
/**
- * @return When the notification should auto-dismiss itself, based on
- * {@link SystemClock#elapsedRealtime()}
- */
- private long calculateFinishTime() {
- int requestedTimeOutMs;
- if (isStickyForSomeTime()) {
- requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
- } else {
- requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
- }
- final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
- return mPostTime + duration + (extended ? mExtensionTime : 0);
- }
-
- /**
* Get user-preferred or default timeout duration. The larger one will be returned.
* @return milliseconds before auto-dismiss
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
index 388d357b3b15..00b05cbd7bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
@@ -106,13 +106,23 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
)
}
- fun logAvalancheDuration(thisKey: String, duration: Int, reason: String, nextKey: String) {
+ fun logAvalancheDuration(
+ thisKey: String,
+ duration: RemainingDuration,
+ reason: String,
+ nextKey: String,
+ ) {
+ val durationMs =
+ when (duration) {
+ is RemainingDuration.UpdatedDuration -> duration.duration
+ is RemainingDuration.HideImmediately -> 0
+ }
buffer.log(
TAG,
INFO,
{
str1 = thisKey
- int1 = duration
+ int1 = durationMs
str2 = reason
str3 = nextKey
},
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
index ca9d498c0e7f..f52d351cfb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
@@ -50,7 +50,9 @@ object NotificationsHunSharedAnimationValues {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt
new file mode 100644
index 000000000000..fd7f4e87e8e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.headsup
+
+/** Models how much longer a HUN should be displayed. */
+sealed interface RemainingDuration {
+ /** This HUN should be hidden immediately, regardless of any minimum time enforcements. */
+ data object HideImmediately : RemainingDuration
+
+ /** This HUN should hide after [duration] milliseconds have occurred. */
+ data class UpdatedDuration(val duration: Int) : RemainingDuration
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index c512b43c91a3..294b8d35c9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -90,12 +90,12 @@ constructor(
ConcurrentHashMap<String, Job>()
fun addIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
onIconUpdateRequiredListeners += listener
}
fun removeIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
onIconUpdateRequiredListeners -= listener
}
@@ -140,7 +140,7 @@ constructor(
*/
fun createSbIconView(context: Context, entry: NotificationEntry): StatusBarIconView =
traceSection("IconManager.createSbIconView") {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val sbIcon = iconBuilder.createIconView(entry, context)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
@@ -200,7 +200,7 @@ constructor(
/** Update the [StatusBarIconView] for the given [NotificationEntry]. */
fun updateSbIcon(entry: NotificationEntry, iconView: StatusBarIconView) =
traceSection("IconManager.updateSbIcon") {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val (normalIconDescriptor, _) = getIconDescriptors(entry)
val notificationContentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
index aa81ebf22ac6..147a5afea306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -18,11 +18,10 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.view.Display
import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.display.data.repository.PerDisplayRepository
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
@@ -37,7 +36,8 @@ class NotificationIconContainerStatusBarViewBinder
@Inject
constructor(
private val viewModel: NotificationIconContainerStatusBarViewModel,
- @ShadeDisplayAware private val configuration: ConfigurationState,
+ private val configurationStateRepository: PerDisplayRepository<ConfigurationState>,
+ private val defaultConfigurationState: ConfigurationState,
private val systemBarUtilsState: SystemBarUtilsState,
private val failureTracker: StatusBarIconViewBindingFailureTracker,
private val defaultDisplayViewStore: StatusBarNotificationIconViewStore,
@@ -56,12 +56,14 @@ constructor(
lifecycleScope.launch { it.activate() }
}
}
+ val configurationState: ConfigurationState =
+ configurationStateRepository[displayId] ?: defaultConfigurationState
lifecycleScope.launch {
NotificationIconContainerViewBinder.bind(
displayId = displayId,
view = view,
viewModel = viewModel,
- configuration = configuration,
+ configuration = configurationState,
systemBarUtilsState = systemBarUtilsState,
failureTracker = failureTracker,
viewStore = viewStore,
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 c3266fc57c2e..92c87e6403ec 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
@@ -18,10 +18,10 @@ package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.NotificationMediaManager
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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 2a01a14f56aa..777392df67cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -19,19 +19,26 @@ package com.android.systemui.statusbar.notification.promoted
import android.app.Flags
import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
+import android.content.Context
import android.graphics.PorterDuff
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.View.MeasureSpec.UNSPECIFIED
+import android.view.View.MeasureSpec.makeMeasureSpec
import android.view.View.VISIBLE
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewStub
import android.widget.Chronometer
import android.widget.DateTimeView
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
+import androidx.annotation.DimenRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
@@ -42,7 +49,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
@@ -88,22 +97,12 @@ fun AODPromotedNotification(
}
key(content.identity) {
- val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
- val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp)
-
- val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
-
- val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
- val borderShape = RoundedCornerShape(borderRadius)
-
- Box(modifier = modifier.padding(sidePaddingValues)) {
- AODPromotedNotificationView(
- layoutResource = layoutResource,
- content = content,
- audiblyAlertedIconVisible = audiblyAlertedIconVisible,
- modifier = Modifier.border(borderStroke, borderShape),
- )
- }
+ AODPromotedNotificationView(
+ layoutResource = layoutResource,
+ content = content,
+ audiblyAlertedIconVisible = audiblyAlertedIconVisible,
+ modifier = modifier,
+ )
}
}
@@ -114,27 +113,91 @@ fun AODPromotedNotificationView(
audiblyAlertedIconVisible: Boolean,
modifier: Modifier = Modifier,
) {
- AndroidView(
- factory = { context ->
- val view =
- traceSection("$TAG.inflate") {
- LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
- }
-
- val updater =
- traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(view) }
-
- view.setTag(viewUpdaterTagId, updater)
-
- view
- },
- update = { view ->
- val updater = view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
-
- traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) }
- },
- modifier = modifier,
- )
+ val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
+ val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp)
+
+ val boxModifier = modifier.padding(sidePaddingValues)
+
+ val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
+
+ val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
+ val borderShape = RoundedCornerShape(borderRadius)
+
+ val maxHeight =
+ with(LocalDensity.current) {
+ scaledFontHeight(systemuiR.dimen.notification_max_height_for_promoted_ongoing)
+ .toPx()
+ }
+ .toInt()
+
+ val viewModifier = Modifier.border(borderStroke, borderShape)
+
+ Box(modifier = boxModifier) {
+ AndroidView(
+ factory = { context ->
+ val notif =
+ traceSection("$TAG.inflate") {
+ LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
+ }
+ val updater =
+ traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(notif) }
+
+ val frame = FrameLayoutWithMaxHeight(maxHeight, context)
+ frame.addView(notif)
+ frame.setTag(viewUpdaterTagId, updater)
+
+ frame
+ },
+ update = { frame ->
+ val updater = frame.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
+
+ traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) }
+ frame.maxHeight = maxHeight
+ },
+ modifier = viewModifier,
+ )
+ }
+}
+
+private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : FrameLayout(context) {
+ var maxHeight = maxHeight
+ set(value) {
+ if (field != value) {
+ field = value
+ requestLayout()
+ }
+ }
+
+ // This mirrors the logic in NotificationContentView.onMeasure.
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ if (childCount < 1) {
+ return
+ }
+
+ val child = getChildAt(0)
+ val childLayoutHeight = child.layoutParams.height
+ val childHeightSpec =
+ if (childLayoutHeight >= 0) {
+ makeMeasureSpec(maxHeight.coerceAtMost(childLayoutHeight), EXACTLY)
+ } else {
+ makeMeasureSpec(maxHeight, AT_MOST)
+ }
+ measureChildWithMargins(child, widthMeasureSpec, 0, childHeightSpec, 0)
+ val childMeasuredHeight = child.measuredHeight
+
+ val ownHeightMode = MeasureSpec.getMode(heightMeasureSpec)
+ val ownHeightSize = MeasureSpec.getSize(heightMeasureSpec)
+
+ val ownMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val ownMeasuredHeight =
+ if (ownHeightMode != UNSPECIFIED) {
+ childMeasuredHeight.coerceAtMost(ownHeightSize)
+ } else {
+ childMeasuredHeight
+ }
+
+ setMeasuredDimension(ownMeasuredWidth, ownMeasuredHeight)
+ }
}
private val PromotedNotificationContentModel.layoutResource: Int?
@@ -521,6 +584,11 @@ private enum class AodPromotedNotificationColor(val colorInt: Int) {
val brush = SolidColor(androidx.compose.ui.graphics.Color(colorInt))
}
+@Composable
+private fun scaledFontHeight(@DimenRes dimenId: Int): Dp {
+ return dimensionResource(dimenId) * LocalDensity.current.fontScale.coerceAtLeast(1f)
+}
+
private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag
private const val TAG = "AODPromotedNotification"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index 2aafe8c81381..d35c3b617246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -20,6 +20,7 @@ import android.app.Notification
import android.app.Notification.BigPictureStyle
import android.app.Notification.BigTextStyle
import android.app.Notification.CallStyle
+import android.app.Notification.EXTRA_BIG_TEXT
import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
import android.app.Notification.EXTRA_PROGRESS
import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE
@@ -27,8 +28,10 @@ import android.app.Notification.EXTRA_PROGRESS_MAX
import android.app.Notification.EXTRA_SUB_TEXT
import android.app.Notification.EXTRA_TEXT
import android.app.Notification.EXTRA_TITLE
+import android.app.Notification.EXTRA_TITLE_BIG
import android.app.Notification.EXTRA_VERIFICATION_ICON
import android.app.Notification.EXTRA_VERIFICATION_TEXT
+import android.app.Notification.InboxStyle
import android.app.Notification.ProgressStyle
import android.content.Context
import android.graphics.drawable.Icon
@@ -105,8 +108,8 @@ constructor(
contentBuilder.shortCriticalText = notification.shortCriticalText()
contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
- contentBuilder.title = notification.title()
- contentBuilder.text = notification.text()
+ contentBuilder.title = notification.resolveTitle(recoveredBuilder.style)
+ contentBuilder.text = notification.resolveText(recoveredBuilder.style)
contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider)
contentBuilder.oldProgress = notification.oldProgress()
@@ -127,8 +130,39 @@ constructor(
private fun Notification.title(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE)
+ private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG)
+
+ private fun Notification.Style.bigTitleOverridesTitle(): Boolean {
+ return when (this) {
+ is BigTextStyle,
+ is BigPictureStyle,
+ is InboxStyle -> true
+ else -> false
+ }
+ }
+
+ private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? {
+ return if (style?.bigTitleOverridesTitle() == true) {
+ bigTitle()
+ } else {
+ null
+ } ?: title()
+ }
+
private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_TEXT)
+ private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT)
+
+ private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle
+
+ private fun Notification.resolveText(style: Notification.Style?): CharSequence? {
+ return if (style?.bigTextOverridesText() == true) {
+ bigText()
+ } else {
+ null
+ } ?: text()
+ }
+
private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT)
private fun Notification.shortCriticalText(): String? {
@@ -233,13 +267,13 @@ constructor(
private fun BigPictureStyle.extractContent(
contentBuilder: PromotedNotificationContentModel.Builder
) {
- // TODO?
+ // Big title is handled in resolveTitle, and big picture is unsupported.
}
private fun BigTextStyle.extractContent(
contentBuilder: PromotedNotificationContentModel.Builder
) {
- // TODO?
+ // Big title and big text are handled in resolveTitle and resolveText.
}
private fun CallStyle.extractContent(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
index 632421980772..da59a40a1624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
@@ -50,7 +50,9 @@ object PromotedNotificationUi {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
index fa1f32cc367e..c6e3da1c5750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -49,7 +49,9 @@ object PromotedNotificationUiAod {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
index cb0d67403521..adeddde8ccc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -50,7 +50,9 @@ object PromotedNotificationUiForceExpanded {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
index 4bc685423659..ec4ee4560ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -34,10 +33,7 @@ constructor(
) : FlowDumperImpl(dumpManager) {
/** The content to show as the promoted notification on AOD */
val content: Flow<PromotedNotificationContentModel?> =
- promotedNotificationsInteractor.topPromotedNotificationContent
+ promotedNotificationsInteractor.aodPromotedNotification
- val isPresent: Flow<Boolean> =
- content
- .map { (it != null) && (it.style != Style.Ineligible) }
- .dumpWhileCollecting("isPresent")
+ val isPresent: Flow<Boolean> = content.map { it != null }.dumpWhileCollecting("isPresent")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt
new file mode 100644
index 000000000000..14295357c54f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.promoted.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** A class which can receive both a demotion signal and a single handler of that signal */
+@SysUISingleton
+class PackageDemotionInteractor @Inject constructor() {
+ private var demotionSignalHandler: ((packageName: String, uid: Int) -> Unit)? = null
+
+ /**
+ * called after sending a the demotion signal to
+ * [android.app.INotificationManager.setCanBePromoted]
+ */
+ fun onPackageDemoted(packageName: String, uid: Int) {
+ demotionSignalHandler?.invoke(packageName, uid)
+ }
+
+ /** sets the [handler] that will be called when [onPackageDemoted] is called. */
+ fun setOnPackageDemotionHandler(handler: (packageName: String, uid: Int) -> Unit) {
+ demotionSignalHandler = handler
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
index 1015cfbefc41..a99ca072b6c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
@@ -22,6 +22,8 @@ import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInter
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Ineligible
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -68,9 +70,6 @@ constructor(
private fun OngoingCallModel.getNotifData(): NotifAndPromotedContent? =
when (this) {
is OngoingCallModel.InCall -> NotifAndPromotedContent(notificationKey, promotedContent)
- is OngoingCallModel.InCallWithVisibleApp ->
- // TODO(b/395989259): support InCallWithVisibleApp when it has notif data
- null
is OngoingCallModel.NoCall -> null
}
@@ -83,13 +82,13 @@ constructor(
.map { list -> list.firstNotNullOfOrNull { it.promotedContent } }
.distinctUntilNewInstance()
- /** This is the top-most promoted notification, which should avoid regular changing. */
- val topPromotedNotificationContent: Flow<PromotedNotificationContentModel?> =
+ /** This is the AOD promoted notification, which should avoid regular changing. */
+ val aodPromotedNotification: Flow<PromotedNotificationContentModel?> =
combine(
topPromotedChipNotification,
activeNotificationsInteractor.topLevelRepresentativeNotifications,
) { topChipNotif, topLevelNotifs ->
- topChipNotif ?: topLevelNotifs.firstNotNullOfOrNull { it.promotedContent }
+ topChipNotif?.takeIfAodEligible() ?: topLevelNotifs.firstAodEligibleOrNull()
}
// #equals() can be a bit expensive on this object, but this flow will regularly try to
// emit the same immutable instance over and over, so just prevent that.
@@ -105,6 +104,16 @@ constructor(
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
+ private fun List<ActiveNotificationModel>.firstAodEligibleOrNull():
+ PromotedNotificationContentModel? {
+ return this.firstNotNullOfOrNull { it.promotedContent?.takeIfAodEligible() }
+ }
+
+ private fun PromotedNotificationContentModel.takeIfAodEligible():
+ PromotedNotificationContentModel? {
+ return this.takeUnless { it.style == Ineligible }
+ }
+
/**
* Returns flow where all subsequent repetitions of the same object instance are filtered out.
*/
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 6837cb2a6292..e76867373139 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
@@ -40,6 +40,7 @@ import android.view.animation.Interpolator;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
+import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.common.shared.colors.SurfaceEffectColors;
import com.android.systemui.res.R;
@@ -101,7 +102,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private ValueAnimator mBackgroundColorAnimator;
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
- private int mNormalColor;
+ protected int mNormalColor;
+ protected int mOpaqueColor;
private boolean mIsBelowSpeedBump;
private long mLastActionUpTime;
@@ -128,14 +130,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
updateColors();
}
- private void updateColors() {
- if (usesTransparentBackground()) {
+ protected void updateColors() {
+ if (notificationRowTransparency()) {
mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
+ mOpaqueColor = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainer);
} else {
mNormalColor = mContext.getColor(
com.android.internal.R.color.materialColorSurfaceContainerHigh);
}
- setBackgroundToNormalColor();
mTintedRippleColor = mContext.getColor(
R.color.notification_ripple_tinted_color);
mNormalRippleColor = mContext.getColor(
@@ -146,12 +149,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mOverrideAmount = 0.0f;
}
- private void setBackgroundToNormalColor() {
- if (mBackgroundNormal != null) {
- mBackgroundNormal.setNormalColor(mNormalColor);
- }
- }
-
/**
* Reload background colors from resources and invalidate views.
*/
@@ -181,7 +178,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal = findViewById(R.id.backgroundNormal);
mFakeShadow = findViewById(R.id.fake_shadow);
mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
- setBackgroundToNormalColor();
initBackground();
updateBackgroundTint();
updateOutlineAlpha();
@@ -347,7 +343,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
protected boolean usesTransparentBackground() {
- return mIsBlurSupported && notificationRowTransparency();
+ return false;
}
@Override
@@ -704,7 +700,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (withTint && mBgTint != NO_COLOR) {
return mBgTint;
} else {
- return mNormalColor;
+ if (Flags.notificationRowTransparency()) {
+ return usesTransparentBackground() ? mNormalColor : mOpaqueColor;
+ } else {
+ return mNormalColor;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 640d364895ae..dccc28f5fe49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -34,11 +34,11 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
-import android.widget.Switch
import android.widget.TextView
import com.android.settingslib.Utils
import com.android.systemui.res.R
import com.android.systemui.util.Assert
+import com.google.android.material.materialswitch.MaterialSwitch
/** Half-shelf for notification channel controls */
class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
@@ -139,12 +139,12 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a
class AppControlView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
lateinit var iconView: ImageView
lateinit var channelName: TextView
- lateinit var switch: Switch
+ lateinit var switch: MaterialSwitch
override fun onFinishInflate() {
iconView = requireViewById(R.id.icon)
channelName = requireViewById(R.id.app_name)
- switch = requireViewById(R.id.toggle)
+ switch = requireViewById(R.id.material_toggle)
setOnClickListener { switch.toggle() }
}
@@ -155,7 +155,7 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
lateinit var controller: ChannelEditorDialogController
private lateinit var channelName: TextView
private lateinit var channelDescription: TextView
- private lateinit var switch: Switch
+ private lateinit var switch: MaterialSwitch
private val highlightColor: Int
var gentle = false
@@ -175,7 +175,7 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
super.onFinishInflate()
channelName = requireViewById(R.id.channel_name)
channelDescription = requireViewById(R.id.channel_description)
- switch = requireViewById(R.id.toggle)
+ switch = requireViewById(R.id.material_toggle)
switch.setOnCheckedChangeListener { _, b ->
channel?.let {
controller.proposeEditForChannel(
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 3ef1fd2275a2..76830646587d 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
@@ -19,9 +19,12 @@ package com.android.systemui.statusbar.notification.row;
import static android.app.Flags.notificationsRedesignTemplates;
import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
import static com.android.systemui.Flags.notificationRowTransparency;
import static com.android.systemui.Flags.notificationsPinnedHunInShade;
+import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
@@ -65,6 +68,7 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.Chronometer;
@@ -78,6 +82,7 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -99,6 +104,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -110,6 +116,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -117,9 +124,11 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
@@ -178,7 +187,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mShowSnooze = false;
private boolean mIsFaded;
- private boolean mIsPromotedOngoing = false;
+ private boolean mHasStatusBarChipDuringHeadsUpAnimation = false;
@Nullable
public ImageModelIndex mImageModelIndex = null;
@@ -401,10 +410,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void toggleExpansionState(View v, boolean shouldLogExpandClickMetric) {
- boolean isGroupRoot = NotificationBundleUi.isEnabled()
- ? mGroupMembershipManager.isGroupRoot(mEntryAdapter)
- : mGroupMembershipManager.isGroupSummary(mEntry);
- if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot) {
+ if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot()) {
mGroupExpansionChanging = true;
if (NotificationBundleUi.isEnabled()) {
final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntryAdapter);
@@ -426,7 +432,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
onExpansionChanged(true /* userAction */, wasExpanded);
}
} else if (mEnableNonGroupedNotificationExpand) {
- if (v.isAccessibilityFocused()) {
+ if (v != null && v.isAccessibilityFocused()) {
mPrivateLayout.setFocusOnVisibilityChange();
}
boolean nowExpanded;
@@ -866,7 +872,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void updateLimitsForView(NotificationContentView layout) {
final int maxExpandedHeight;
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing;
} else {
maxExpandedHeight = mMaxExpandedHeight;
@@ -947,7 +953,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Nullable
public EntryAdapter getEntryAdapter() {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
return mEntryAdapter;
}
@@ -974,7 +980,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
- updateBackgroundOpacity();
+ updateBackgroundTint();
}
/**
@@ -1345,7 +1351,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight();
}
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
return getMaxExpandHeight();
}
if (mExpandedWhenPinned) {
@@ -1673,23 +1679,35 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected void setBackgroundTintColor(int color) {
- super.setBackgroundTintColor(color);
- NotificationContentView view = getShowingLayout();
- if (view != null) {
- view.setBackgroundTintColor(color);
- }
- if (notificationRowTransparency() && mBackgroundNormal != null) {
- if (NotificationBundleUi.isEnabled()) {
- mBackgroundNormal.setBgIsColorized(
- usesTransparentBackground() && mEntryAdapter.isColorized());
+ if (notificationRowTransparency()) {
+ boolean isColorized = false;
+ if (NotificationBundleUi.isEnabled() && mEntryAdapter != null) {
+ isColorized = mEntryAdapter.isColorized();
} else {
if (mEntry != null) {
- mBackgroundNormal.setBgIsColorized(
- usesTransparentBackground()
- && mEntry.getSbn().getNotification().isColorized());
+ isColorized = mEntry.getSbn().getNotification().isColorized();
+ }
+ }
+ boolean isTransparent = usesTransparentBackground();
+ if (isColorized) {
+ // For colorized notifications, use a color that matches the tint color at 90% alpha
+ // when the row is transparent.
+ color = ColorUtils.setAlphaComponent(
+ color, (int) (0xFF * (isTransparent ? 0.9f : 1)));
+ } else {
+ // For non-colorized notifications, use the semi-transparent normal color token
+ // when the row is transparent, and the opaque color token otherwise.
+ if (!isTransparent && mBgTint == NO_COLOR) {
+ color = mOpaqueColor;
}
}
}
+
+ super.setBackgroundTintColor(color);
+ NotificationContentView view = getShowingLayout();
+ if (view != null) {
+ view.setBackgroundTintColor(color);
+ }
}
public void closeRemoteInput() {
@@ -1816,6 +1834,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
);
}
+ /**
+ * Init the bundle header view. The ComposeView is initialized within with the passed viewModel.
+ * This can only be init once and not in conjunction with any other header view.
+ */
+ public void initBundleHeader(@NonNull BundleHeaderViewModelImpl bundleHeaderViewModel) {
+ if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) return;
+ NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull();
+ bundleHeaderViewModel.setOnExpandClickListener(mExpandClickListener);
+
+ childrenContainer.initBundleHeader(bundleHeaderViewModel);
+
+ if (TransparentHeaderFix.isEnabled()) {
+ updateBackgroundForGroupState();
+ }
+ }
+
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
boolean wasAboveShelf = isAboveShelf();
boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning;
@@ -2074,7 +2108,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs, UserHandle user) {
this(context, attrs, userContextForEntry(context, user));
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
}
/**
@@ -2122,7 +2156,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* Initialize row.
*/
public void initialize(
- NotificationEntry entry,
+ EntryAdapter entryAdapter,
+ PipelineEntry entry,
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
String appName,
@NonNull String notificationKey,
@@ -2150,11 +2185,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationRebindingTracker notificationRebindingTracker) {
if (NotificationBundleUi.isEnabled()) {
+ mEntryAdapter = entryAdapter;
// TODO (b/395857098): remove when all usages are migrated
- mEntryAdapter = entry.getEntryAdapter();
- mEntry = entry;
+ mEntry = (NotificationEntry) entry;
} else {
- mEntry = entry;
+ mEntry = (NotificationEntry) entry;
}
mAppName = appName;
mRebindingTracker = notificationRebindingTracker;
@@ -2755,7 +2790,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return false;
}
-
public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
if (params == null) {
// `null` params indicates the animation is over, which means we can't access
@@ -2920,7 +2954,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren && !shouldShowPublic()) {
return !mChildrenExpanded;
}
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
return false;
}
return mEnableNonGroupedNotificationExpand && mExpandable;
@@ -2931,16 +2965,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.updateExpandButtons(isExpandable());
}
+
/**
- * Set this notification to be promoted ongoing
+ * Sets whether the status bar is showing a chip corresponding to this notification.
+ *
+ * Only set when this notification's heads-up status changes since that's the only time it's
+ * relevant.
*/
- public void setPromotedOngoing(boolean promotedOngoing) {
- if (PromotedNotificationUiForceExpanded.isUnexpectedlyInLegacyMode()) {
+ public void setHasStatusBarChipDuringHeadsUpAnimation(boolean hasStatusBarChip) {
+ if (StatusBarNotifChips.isUnexpectedlyInLegacyMode()) {
return;
}
+ mHasStatusBarChipDuringHeadsUpAnimation = hasStatusBarChip;
+ }
- mIsPromotedOngoing = promotedOngoing;
- setExpandable(!mIsPromotedOngoing);
+ /**
+ * Returns true if the status bar is showing a chip corresponding to this notification during a
+ * heads-up appear or disappear animation.
+ *
+ * Note that this value is only set when this notification's heads-up status changes since
+ * that's the only time it's relevant.
+ */
+ public boolean hasStatusBarChipDuringHeadsUpAnimation() {
+ return StatusBarNotifChips.isEnabled() && mHasStatusBarChipDuringHeadsUpAnimation;
}
@Override
@@ -3018,11 +3065,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void setUserLocked(boolean userLocked) {
- if (isPromotedOngoing()) return;
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) return;
mUserLocked = userLocked;
mPrivateLayout.setUserExpanding(userLocked);
- mPublicLayout.setUserExpanding(userLocked);
+ if (android.app.Flags.expandingPublicView()) {
+ mPublicLayout.setUserExpanding(userLocked);
+ }
// This is intentionally not guarded with mIsSummaryWithChildren since we might have had
// children but not anymore.
if (mChildrenContainer != null) {
@@ -3079,7 +3128,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setOnKeyguard(onKeyguard);
}
}
- updateBackgroundOpacity();
+ updateBackgroundTint();
}
}
@@ -3150,6 +3199,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGroupExpansionManager.isGroupExpanded(mEntry);
}
+ private boolean isGroupRoot() {
+ return NotificationBundleUi.isEnabled()
+ ? mGroupMembershipManager.isGroupRoot(mEntryAdapter)
+ : mGroupMembershipManager.isGroupSummary(mEntry);
+ }
+
private void onAttachedChildrenCountChanged() {
final boolean wasSummary = mIsSummaryWithChildren;
mIsSummaryWithChildren = mChildrenContainer != null
@@ -3199,7 +3254,23 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isPromotedOngoing() {
- return PromotedNotificationUiForceExpanded.isEnabled() && mIsPromotedOngoing;
+ if (!PromotedNotificationUi.isEnabled()) {
+ return false;
+ }
+
+ if (NotificationBundleUi.isEnabled()) {
+ final EntryAdapter entryAdapter = mEntryAdapter;
+ if (entryAdapter == null) {
+ return false;
+ }
+ return entryAdapter.isPromotedOngoing();
+ } else {
+ final NotificationEntry entry = mEntry;
+ if (entry == null) {
+ return false;
+ }
+ return entry.isPromotedOngoing();
+ }
}
private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) {
@@ -3228,6 +3299,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
+ * Is this row currently showing an expanded state? This method is different from
+ * {@link #isExpanded()}, because it also handles groups, and pinned notifications.
+ */
+ private boolean isShowingExpanded() {
+ if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot()) {
+ // is group and expanded?
+ return isGroupExpanded();
+ } else if (mEnableNonGroupedNotificationExpand) {
+ if (isPinned()) {
+ // is pinned and expanded?
+ return mExpandedWhenPinned;
+ } else {
+ // is regular notification and expanded?
+ return isExpanded();
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Check whether the view state is currently expanded. This is given by the system in {@link
* #setSystemExpanded(boolean)} and can be overridden by user expansion or
* collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
@@ -3240,7 +3332,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isExpanded(boolean allowOnKeyguard) {
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
return isPromotedNotificationExpanded(allowOnKeyguard);
}
@@ -3909,9 +4001,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void onExpandedByGesture(boolean userExpanded) {
int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
- if (NotificationBundleUi.isEnabled()
- ? mGroupMembershipManager.isGroupRoot(mEntryAdapter)
- : mGroupMembershipManager.isGroupSummary(mEntry)) {
+ if (isGroupRoot()) {
event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
}
mMetricsLogger.action(event, userExpanded);
@@ -3964,6 +4054,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mExpansionChangedListener != null) {
mExpansionChangedListener.onExpansionChanged(nowExpanded);
}
+ if (notificationRowAccessibilityExpanded()) {
+ notifyAccessibilityContentExpansionChanged();
+ }
+ }
+ }
+
+ private void notifyAccessibilityContentExpansionChanged() {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(CONTENT_CHANGE_TYPE_EXPANDED);
+ sendAccessibilityEventUnchecked(event);
}
}
@@ -3994,33 +4096,50 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
super.onInitializeAccessibilityNodeInfoInternal(info);
final boolean isLongClickable = isNotificationRowLongClickable();
if (isLongClickable) {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
+ info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
}
info.setLongClickable(isLongClickable);
if (canViewBeDismissed() && !mIsSnoozed) {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
+ info.addAction(AccessibilityAction.ACTION_DISMISS);
}
- boolean expandable = shouldShowPublic();
- boolean isExpanded = false;
- if (!expandable) {
- if (mIsSummaryWithChildren) {
- expandable = true;
- if (!mIsMinimized || isExpanded()) {
- isExpanded = isGroupExpanded();
+
+ if (notificationRowAccessibilityExpanded()) {
+ if (isAccessibilityExpandable()) {
+ if (isShowingExpanded()) {
+ info.addAction(AccessibilityAction.ACTION_COLLAPSE);
+ info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_FULL);
+ } else {
+ info.addAction(AccessibilityAction.ACTION_EXPAND);
+ info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_COLLAPSED);
}
} else {
- expandable = mPrivateLayout.isContentExpandable();
- isExpanded = isExpanded();
+ info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_UNDEFINED);
}
- }
- if (expandable && !mIsSnoozed) {
- if (isExpanded) {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
- } else {
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+ } else {
+ boolean expandable = shouldShowPublic();
+ boolean isExpanded = false;
+ if (!expandable) {
+ if (mIsSummaryWithChildren) {
+ expandable = true;
+ if (!mIsMinimized || isExpanded()) {
+ isExpanded = isGroupExpanded();
+ }
+ } else {
+ expandable = mPrivateLayout.isContentExpandable();
+ isExpanded = isExpanded();
+ }
+ }
+
+ if (expandable) {
+ if (isExpanded) {
+ info.addAction(AccessibilityAction.ACTION_COLLAPSE);
+ } else {
+ info.addAction(AccessibilityAction.ACTION_EXPAND);
+ }
}
}
+
NotificationMenuRowPlugin provider = getProvider();
if (provider != null) {
MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
@@ -4033,6 +4152,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ /** @return whether this row's expansion state can be toggled by an accessibility action. */
+ private boolean isAccessibilityExpandable() {
+ // don't add expand accessibility actions to snoozed notifications
+ return !mIsSnoozed && isContentExpandable();
+ }
+
@Override
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (super.performAccessibilityActionInternal(action, arguments)) {
@@ -4296,8 +4421,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
+ (!shouldShowPublic() && mIsSummaryWithChildren));
pw.print(", mShowNoBackground: " + mShowNoBackground);
pw.print(", clipBounds: " + getClipBounds());
- if (PromotedNotificationUiForceExpanded.isEnabled()) {
- pw.print(", isPromotedOngoing: " + isPromotedOngoing());
+ pw.print(", isPromotedOngoing: " + isPromotedOngoing());
+ if (notificationRowAccessibilityExpanded()) {
+ pw.print(", isShowingExpanded: " + isShowingExpanded());
+ pw.print(", isAccessibilityExpandable: " + isAccessibilityExpandable());
}
pw.print(", isExpandable: " + isExpandable());
pw.print(", mExpandable: " + mExpandable);
@@ -4526,11 +4653,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- private void updateBackgroundOpacity() {
- if (mBackgroundNormal != null) {
- // Row background should be opaque when it's displayed as a heads-up notification or
- // displayed on keyguard.
- mBackgroundNormal.setForceOpaque(mIsHeadsUp || mOnKeyguard);
- }
+ @Override
+ protected boolean usesTransparentBackground() {
+ // Row background should be opaque when it's displayed as a heads-up notification or
+ // displayed on keyguard.
+ // TODO(b/388891313): Account for isBlurSupported when it is initialized and updated
+ // correctly.
+ return notificationRowTransparency() && !mIsHeadsUp && !mOnKeyguard;
}
}
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 02e8f4917da4..ac55930f5c11 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
@@ -45,7 +45,11 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FeedbackIcon;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory;
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -56,6 +60,7 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden
import com.android.systemui.statusbar.notification.row.dagger.AppName;
import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -118,8 +123,8 @@ public class ExpandableNotificationRowController implements NotifViewController
private final IStatusBarService mStatusBarService;
private final UiEventLogger mUiEventLogger;
private final MSDLPlayer mMSDLPlayer;
-
private final NotificationSettingsController mSettingsController;
+ private final EntryAdapterFactory mEntryAdapterFactory;
@VisibleForTesting
final NotificationSettingsController.Listener mSettingsListener =
@@ -285,7 +290,8 @@ public class ExpandableNotificationRowController implements NotifViewController
IStatusBarService statusBarService,
UiEventLogger uiEventLogger,
MSDLPlayer msdlPlayer,
- NotificationRebindingTracker notificationRebindingTracker) {
+ NotificationRebindingTracker notificationRebindingTracker,
+ EntryAdapterFactory entryAdapterFactory) {
mView = view;
mListContainer = listContainer;
mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
@@ -322,14 +328,16 @@ public class ExpandableNotificationRowController implements NotifViewController
mStatusBarService = statusBarService;
mUiEventLogger = uiEventLogger;
mMSDLPlayer = msdlPlayer;
+ mEntryAdapterFactory = entryAdapterFactory;
}
/**
* Initialize the controller.
*/
- public void init(NotificationEntry entry) {
+ public void init(PipelineEntry entry) {
mActivatableNotificationViewController.init();
mView.initialize(
+ mEntryAdapterFactory.create(entry),
entry,
mRemoteInputViewSubcomponentFactory,
mAppName,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
index 77135802eced..fe3a856e711e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -18,7 +18,9 @@ package com.android.systemui.statusbar.notification.row
import android.animation.ValueAnimator
import android.content.Context
+import android.content.res.ColorStateList
import android.graphics.Canvas
+import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.Path
@@ -31,9 +33,27 @@ import android.graphics.RectF
import android.graphics.Shader
import com.android.systemui.res.R
import com.android.wm.shell.shared.animation.Interpolators
+import android.graphics.drawable.RippleDrawable
+import androidx.core.content.ContextCompat
class MagicActionBackgroundDrawable(
context: Context,
+) : RippleDrawable(
+ ContextCompat.getColorStateList(
+ context,
+ R.color.notification_ripple_untinted_color
+ ) ?: ColorStateList.valueOf(Color.TRANSPARENT),
+ createBaseDrawable(context), null
+) {
+ companion object {
+ private fun createBaseDrawable(context: Context): Drawable {
+ return BaseBackgroundDrawable(context)
+ }
+ }
+}
+
+class BaseBackgroundDrawable(
+ context: Context,
) : Drawable() {
private val cornerRadius = context.resources.getDimension(R.dimen.magic_action_button_corner_radius)
@@ -45,7 +65,7 @@ class MagicActionBackgroundDrawable(
private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
val bgColor =
context.getColor(
- com.android.internal.R.color.materialColorPrimaryContainer
+ com.android.internal.R.color.materialColorSurfaceContainerHigh
)
color = bgColor
style = Paint.Style.FILL
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt
new file mode 100644
index 000000000000..2b451406eaad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.row
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.ListenerSet
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * Pipeline components can register consumers here to be informed when a notification action is
+ * clicked
+ */
+@SysUISingleton
+class NotificationActionClickManager @Inject constructor() {
+ private val actionClickListeners = ListenerSet<Consumer<NotificationEntry>>()
+
+ fun addActionClickListener(listener: Consumer<NotificationEntry>) {
+ actionClickListeners.addIfAbsent(listener)
+ }
+
+ fun removeActionClickListener(listener: Consumer<NotificationEntry>) {
+ actionClickListeners.remove(listener)
+ }
+
+ fun onNotificationActionClicked(entry: NotificationEntry) {
+ for (listener in actionClickListeners) {
+ listener.accept(entry)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index e1219e88a405..e4997e4f53ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -36,7 +36,6 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dumpable;
import com.android.systemui.common.shared.colors.SurfaceEffectColors;
@@ -53,7 +52,6 @@ import java.util.Arrays;
public class NotificationBackgroundView extends View implements Dumpable,
ExpandableNotificationRow.DismissButtonTargetVisibilityListener {
- private static final int MAX_ALPHA = 0xFF;
private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
@@ -74,8 +72,6 @@ public class NotificationBackgroundView extends View implements Dumpable,
private final ColorStateList mLightColoredStatefulColors;
private final ColorStateList mDarkColoredStatefulColors;
private int mNormalColor;
- private boolean mBgIsColorized = false;
- private boolean mForceOpaque = false;
private final int convexR = 9;
private final int concaveR = 22;
@@ -89,13 +85,15 @@ public class NotificationBackgroundView extends View implements Dumpable,
R.color.notification_state_color_light);
mDarkColoredStatefulColors = getResources().getColorStateList(
R.color.notification_state_color_dark);
+ if (notificationRowTransparency()) {
+ mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
+ } else {
+ mNormalColor = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ }
mFocusOverlayStroke = getResources().getDimension(R.dimen.notification_focus_stroke_width);
}
- public void setNormalColor(int color) {
- mNormalColor = color;
- }
-
@Override
public void onTargetVisibilityChanged(boolean targetVisible) {
if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
@@ -141,29 +139,6 @@ public class NotificationBackgroundView extends View implements Dumpable,
}
}
- /**
- * A way to tell whether the background has been colorized.
- */
- public boolean isColorized() {
- return mBgIsColorized;
- }
-
- /**
- * A way to inform this class whether the background has been colorized.
- * We need to know this, in order to *not* override that color.
- */
- public void setBgIsColorized(boolean b) {
- mBgIsColorized = b;
- }
-
- /** Sets if the background should be opaque. */
- public void setForceOpaque(boolean forceOpaque) {
- mForceOpaque = forceOpaque;
- if (notificationRowTransparency()) {
- updateBaseLayerColor();
- }
- }
-
private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
// TODO(b/365585705): Adapt to RTL after the UX design is finalized.
@@ -320,31 +295,21 @@ public class NotificationBackgroundView extends View implements Dumpable,
return ((LayerDrawable) mBackground).getDrawable(1);
}
- private void updateBaseLayerColor() {
- // BG base layer being a drawable, there isn't a method like setColor() to color it.
- // Instead, we set a color filter that essentially replaces every pixel of the drawable.
- // For non-colorized notifications, this function specifies a new color token.
- // For colorized notifications, this uses a color that matches the tint color at 90% alpha.
- int color = isColorized()
- ? ColorUtils.setAlphaComponent(mTintColor, (int) (MAX_ALPHA * 0.9f))
- : SurfaceEffectColors.surfaceEffect1(getContext());
- if (mForceOpaque) {
- color = ColorUtils.setAlphaComponent(color, MAX_ALPHA);
- }
- getBaseBackgroundLayer().setColorFilter(
- new PorterDuffColorFilter(
- color,
- PorterDuff.Mode.SRC)); // SRC operator discards the drawable's color+alpha
- }
-
public void setTint(int tintColor) {
Drawable baseLayer = getBaseBackgroundLayer();
- baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
- baseLayer.setTint(tintColor);
- mTintColor = tintColor;
if (notificationRowTransparency()) {
- updateBaseLayerColor();
+ // BG base layer being a drawable, there isn't a method like setColor() to color it.
+ // Instead, we set a color filter that essentially replaces every pixel of the drawable.
+ baseLayer.setColorFilter(
+ new PorterDuffColorFilter(
+ tintColor,
+ // SRC operator discards the drawable's color+alpha
+ PorterDuff.Mode.SRC));
+ } else {
+ baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
+ baseLayer.setTint(tintColor);
}
+ mTintColor = tintColor;
setStatefulColors();
invalidate();
}
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 daa598b5f297..ff4b835eb3c0 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
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
@@ -57,10 +57,8 @@ import com.android.systemui.statusbar.notification.ConversationNotificationProce
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
@@ -238,7 +236,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
);
}
if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
- if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ if (bindParams.redactionType == REDACTION_TYPE_OTP) {
result.mPublicInflatedSingleLineViewModel =
SingleLineViewInflater.inflateSingleLineViewModel(
entry.getSbn().getNotification(),
@@ -441,8 +439,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationRowContentBinderLogger logger) {
return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
InflationProgress result = new InflationProgress();
+
+ // inflating the contracted view is the legacy invalidation trigger
+ boolean reinflating = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0;
// create an image inflater
- result.mRowImageInflater = RowImageInflater.newInstance(row.mImageModelIndex);
+ result.mRowImageInflater = RowImageInflater.newInstance(row.mImageModelIndex,
+ reinflating);
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
logger.logAsyncTaskProgress(row.getLoggingKey(), "creating contracted remote view");
@@ -467,7 +469,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
logger.logAsyncTaskProgress(row.getLoggingKey(), "creating public remote view");
if (LockscreenOtpRedaction.isEnabled()
- && bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ && bindParams.redactionType == REDACTION_TYPE_OTP) {
result.newPublicView = createSensitiveContentMessageNotification(
NotificationBundleUi.isEnabled()
? row.getEntryAdapter().getSbn().getNotification()
@@ -1003,10 +1005,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
entry.setPromotedNotificationContentModel(result.mPromotedContent);
}
- if (PromotedNotificationUiForceExpanded.isEnabled()) {
- row.setPromotedOngoing(entry.isOngoingPromoted());
- }
-
boolean setRepliesAndActions = true;
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
if (result.inflatedContentView != null) {
@@ -1357,7 +1355,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
- if (mBindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ if (mBindParams.redactionType == REDACTION_TYPE_OTP) {
result.mPublicInflatedSingleLineViewModel =
SingleLineViewInflater.inflateSingleLineViewModel(
mEntry.getSbn().getNotification(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 193203720aa5..e9993ae31514 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Flags.notificationsRedesignTemplates;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Flags;
@@ -267,6 +269,7 @@ public class NotificationContentView extends FrameLayout implements Notification
mNotificationMaxHeight = maxHeight;
}
+ // This logic is mirrored in FrameLayoutWithMaxHeight.onMeasure in AODPromotedNotification.kt.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -293,8 +296,8 @@ public class NotificationContentView extends FrameLayout implements Notification
useExactly = true;
}
int spec = MeasureSpec.makeMeasureSpec(size, useExactly
- ? MeasureSpec.EXACTLY
- : MeasureSpec.AT_MOST);
+ ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST);
measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
@@ -719,16 +722,19 @@ public class NotificationContentView extends FrameLayout implements Notification
* height, the notification is clipped instead of being further shrunk.
*/
private int getMinContentHeightHint() {
+ int actionListHeight = mContext.getResources().getDimensionPixelSize(
+ notificationsRedesignTemplates()
+ ? com.android.internal.R.dimen.notification_2025_action_list_height
+ : com.android.internal.R.dimen.notification_action_list_height);
if (mIsChildInGroup && isVisibleOrTransitioning(VISIBLE_TYPE_SINGLELINE)) {
- return mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_action_list_height);
+ return actionListHeight;
}
// Transition between heads-up & expanded, or pinned.
if (mHeadsUpChild != null && mExpandedChild != null) {
boolean transitioningBetweenHunAndExpanded =
isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
- isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
+ isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
&& (mIsHeadsUp || mHeadsUpAnimatingAway)
&& mContainingNotification.canShowHeadsUp();
@@ -756,9 +762,7 @@ public class NotificationContentView extends FrameLayout implements Notification
} else if (mExpandedChild != null) {
hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
} else if (mContractedChild != null) {
- hint = getViewHeight(VISIBLE_TYPE_CONTRACTED)
- + mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_action_list_height);
+ hint = getViewHeight(VISIBLE_TYPE_CONTRACTED) + actionListHeight;
} else {
hint = getMinHeight();
}
@@ -1053,8 +1057,8 @@ public class NotificationContentView extends FrameLayout implements Notification
// the original type
final int visibleType = (
isGroupExpanded() || mContainingNotification.isUserLocked())
- ? calculateVisibleType()
- : getVisibleType();
+ ? calculateVisibleType()
+ : getVisibleType();
return getBackgroundColor(visibleType);
}
@@ -1240,7 +1244,14 @@ public class NotificationContentView extends FrameLayout implements Notification
height = mContentHeight;
}
int expandedVisualType = getVisualTypeForHeight(height);
- int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
+ final boolean shouldShowSingleLineView = mIsChildInGroup && !isGroupExpanded();
+ final boolean isSingleLineViewPresent = mSingleLineView != null;
+
+ if (shouldShowSingleLineView && !isSingleLineViewPresent) {
+ Log.e(TAG, "calculateVisibleType: SingleLineView is not available!");
+ }
+
+ final int collapsedVisualType = shouldShowSingleLineView && isSingleLineViewPresent
? VISIBLE_TYPE_SINGLELINE
: getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
return mTransformationStartVisibleType == collapsedVisualType
@@ -1261,7 +1272,13 @@ public class NotificationContentView extends FrameLayout implements Notification
if (!noExpandedChild && viewHeight == getViewHeight(VISIBLE_TYPE_EXPANDED)) {
return VISIBLE_TYPE_EXPANDED;
}
- if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
+ final boolean shouldShowSingleLineView = mIsChildInGroup && !isGroupExpanded();
+ final boolean isSingleLinePresent = mSingleLineView != null;
+ if (shouldShowSingleLineView && !isSingleLinePresent) {
+ Log.e(TAG, "getVisualTypeForHeight: singleLineView is not available.");
+ }
+
+ if (!mUserExpanding && shouldShowSingleLineView && isSingleLinePresent) {
return VISIBLE_TYPE_SINGLELINE;
}
@@ -1276,7 +1293,7 @@ public class NotificationContentView extends FrameLayout implements Notification
if (noExpandedChild || (mContractedChild != null
&& viewHeight <= getViewHeight(VISIBLE_TYPE_CONTRACTED)
&& (!mIsChildInGroup || isGroupExpanded()
- || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
+ || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
return VISIBLE_TYPE_CONTRACTED;
} else if (!noExpandedChild) {
return VISIBLE_TYPE_EXPANDED;
@@ -1576,10 +1593,13 @@ public class NotificationContentView extends FrameLayout implements Notification
return;
}
ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
- View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- ViewGroup actionListMarginTarget = layout.findViewById(
- com.android.internal.R.id.notification_action_list_margin_target);
- if (bubbleButton == null || actionContainer == null) {
+ // With the new design, the actions_container should always be visible to act as padding
+ // when there are no actions. We're making its child visible/invisible instead.
+ View actionsContainerForVisibilityChange = layout.findViewById(
+ notificationsRedesignTemplates()
+ ? com.android.internal.R.id.actions_container_layout
+ : com.android.internal.R.id.actions_container);
+ if (bubbleButton == null || actionsContainerForVisibilityChange == null) {
return;
}
@@ -1597,15 +1617,14 @@ public class NotificationContentView extends FrameLayout implements Notification
bubbleButton.setImageDrawable(d);
bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
bubbleButton.setVisibility(VISIBLE);
- actionContainer.setVisibility(VISIBLE);
- // Set notification_action_list_margin_target's bottom margin to 0 when showing bubble
- if (actionListMarginTarget != null) {
- ViewGroup.LayoutParams lp = actionListMarginTarget.getLayoutParams();
- if (lp instanceof ViewGroup.MarginLayoutParams) {
- final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
- if (mlp.bottomMargin > 0) {
- mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, 0);
- }
+ actionsContainerForVisibilityChange.setVisibility(VISIBLE);
+ if (!notificationsRedesignTemplates()) {
+ // Set notification_action_list_margin_target's bottom margin to 0 when showing
+ // bubble
+ ViewGroup actionListMarginTarget = layout.findViewById(
+ com.android.internal.R.id.notification_action_list_margin_target);
+ if (actionListMarginTarget != null) {
+ removeBottomMargin(actionListMarginTarget);
}
}
} else {
@@ -1613,6 +1632,16 @@ public class NotificationContentView extends FrameLayout implements Notification
}
}
+ private static void removeBottomMargin(ViewGroup actionListMarginTarget) {
+ ViewGroup.LayoutParams lp = actionListMarginTarget.getLayoutParams();
+ if (lp instanceof MarginLayoutParams) {
+ final MarginLayoutParams mlp = (MarginLayoutParams) lp;
+ if (mlp.bottomMargin > 0) {
+ mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, 0);
+ }
+ }
+ }
+
@MainThread
public void setBubblesEnabledForUser(boolean enabled) {
mBubblesEnabledForUser = enabled;
@@ -1636,8 +1665,13 @@ public class NotificationContentView extends FrameLayout implements Notification
return;
}
ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button);
- View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- if (snoozeButton == null || actionContainer == null) {
+ // With the new design, the actions_container should always be visible to act as padding
+ // when there are no actions. We're making its child visible/invisible instead.
+ View actionsContainerForVisibilityChange = layout.findViewById(
+ notificationsRedesignTemplates()
+ ? com.android.internal.R.id.actions_container_layout
+ : com.android.internal.R.id.actions_container);
+ if (snoozeButton == null || actionsContainerForVisibilityChange == null) {
return;
}
// Notification.Builder can 'disable' the snooze button to prevent it from being shown here
@@ -1663,7 +1697,7 @@ public class NotificationContentView extends FrameLayout implements Notification
snoozeButton.setOnClickListener(
mContainingNotification.getSnoozeClickListener(snoozeMenuItem));
snoozeButton.setVisibility(VISIBLE);
- actionContainer.setVisibility(VISIBLE);
+ actionsContainerForVisibilityChange.setVisibility(VISIBLE);
}
private void applySmartReplyView() {
@@ -1687,7 +1721,7 @@ public class NotificationContentView extends FrameLayout implements Notification
: smartReplies.fromAssistant;
boolean editBeforeSending = smartReplies != null
&& mSmartReplyConstants.getEffectiveEditChoicesBeforeSending(
- smartReplies.remoteInput.getEditChoicesBeforeSending());
+ smartReplies.remoteInput.getEditChoicesBeforeSending());
mSmartReplyController.smartSuggestionsAdded(mNotificationEntry, numSmartReplies,
numSmartActions, fromAssistant, editBeforeSending);
@@ -2114,8 +2148,8 @@ public class NotificationContentView extends FrameLayout implements Notification
public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
boolean needsPaddings = shouldClipToRounding(getVisibleType(), topRounded, bottomRounded);
if (mUserExpanding) {
- needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded,
- bottomRounded);
+ needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded,
+ bottomRounded);
}
return needsPaddings;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 9a75253295d5..cdb78d99538b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -47,7 +47,6 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -71,6 +70,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
@@ -100,6 +100,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
private final AccessibilityManager mAccessibilityManager;
private final HighPriorityProvider mHighPriorityProvider;
private final ChannelEditorDialogController mChannelEditorDialogController;
+ private final PackageDemotionInteractor mPackageDemotionInteractor;
private final OnUserInteractionCallback mOnUserInteractionCallback;
// Dependencies:
@@ -155,6 +156,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
LauncherApps launcherApps,
ShortcutManager shortcutManager,
ChannelEditorDialogController channelEditorDialogController,
+ PackageDemotionInteractor packageDemotionInteractor,
UserContextProvider contextTracker,
AssistantFeedbackController assistantFeedbackController,
Optional<BubblesManager> bubblesManagerOptional,
@@ -184,6 +186,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mShortcutManager = shortcutManager;
mContextTracker = contextTracker;
mChannelEditorDialogController = channelEditorDialogController;
+ mPackageDemotionInteractor = packageDemotionInteractor;
mAssistantFeedbackController = assistantFeedbackController;
mBubblesManagerOptional = bubblesManagerOptional;
mUiEventLogger = uiEventLogger;
@@ -231,15 +234,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
}
}
- public void onDensityOrFontScaleChanged(NotificationEntry entry) {
- if (!Flags.notificationUndoGutsOnConfigChanged()) {
- Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if"
- + " notificationUndoGutsOnConfigChanged is off");
- }
- setExposedGuts(entry.getGuts());
- bindGuts(entry.getRow());
- }
-
/**
* Sends an intent to open the notification settings for a particular package and optional
* channel.
@@ -291,11 +285,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
}
- private boolean bindGuts(final ExpandableNotificationRow row) {
- row.ensureGutsInflated();
- return bindGuts(row, mGutsMenuItem);
- }
-
@VisibleForTesting
protected boolean bindGuts(final ExpandableNotificationRow row,
NotificationMenuRowPlugin.MenuItem item) {
@@ -429,6 +418,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mIconStyleProvider,
mOnUserInteractionCallback,
mChannelEditorDialogController,
+ mPackageDemotionInteractor,
packageName,
row.getEntry().getChannel(),
row.getEntry(),
@@ -440,6 +430,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
NotificationBundleUi.isEnabled()
? !row.getEntry().isBlockable()
: row.getIsNonblockable(),
+ row.canViewBeDismissed(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
mAssistantFeedbackController,
mMetricsLogger,
@@ -605,6 +596,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
return mNotificationGutsExposed;
}
+ @VisibleForTesting
public void setExposedGuts(NotificationGuts guts) {
mNotificationGutsExposed = guts;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 661122510c6c..b6f4ffce8e00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -74,6 +74,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
@@ -120,6 +121,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private boolean mIsAutomaticChosen;
private boolean mIsSingleDefaultChannel;
private boolean mIsNonblockable;
+ private boolean mIsDismissable;
private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private boolean mIsDeviceProvisioned;
@@ -160,6 +162,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mPressedApply = true;
mGutsContainer.closeControls(v, /* save= */ true);
};
+ private OnClickListener mOnCloseClickListener;
public NotificationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -193,6 +196,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
NotificationIconStyleProvider iconStyleProvider,
OnUserInteractionCallback onUserInteractionCallback,
ChannelEditorDialogController channelEditorDialogController,
+ PackageDemotionInteractor packageDemotionInteractor,
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
@@ -202,6 +206,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
UiEventLogger uiEventLogger,
boolean isDeviceProvisioned,
boolean isNonblockable,
+ boolean isDismissable,
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
MetricsLogger metricsLogger,
@@ -226,11 +231,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mStartingChannelImportance = mSingleNotificationChannel.getImportance();
mWasShownHighPriority = wasShownHighPriority;
mIsNonblockable = isNonblockable;
+ mIsDismissable = isDismissable;
mAppUid = mSbn.getUid();
mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
mShowAutomaticSetting = mAssistantFeedbackController.isFeedbackEnabled();
mUiEventLogger = uiEventLogger;
+ mOnCloseClickListener = onCloseClick;
mIsSystemRegisteredCall = mSbn.getNotification().isStyle(Notification.CallStyle.class)
&& mINotificationManager.isInCall(mSbn.getPackageName(), mSbn.getUid());
@@ -277,6 +284,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable
? VISIBLE : GONE);
+ View dismissButton = findViewById(R.id.inline_dismiss);
+ dismissButton.setOnClickListener(mOnCloseClickListener);
+ dismissButton.setVisibility(dismissButton.hasOnClickListeners() && mIsDismissable
+ ? VISIBLE : GONE);
+
View done = findViewById(R.id.done);
done.setOnClickListener(mOnDismissSettings);
done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index 3586078c9e82..2f94d3220dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
-import android.app.Flags
import android.app.Notification
import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
import android.app.Notification.MessagingStyle
@@ -45,7 +44,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.NotifInflation
import com.android.systemui.res.R
import com.android.systemui.statusbar.InflationTask
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
@@ -53,7 +52,6 @@ import com.android.systemui.statusbar.notification.NmSummarizationUiFlag
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
@@ -499,7 +497,10 @@ constructor(
logger.logAsyncTaskProgress(entry.logKey, "loading RON images")
inflationProgress.rowImageInflater.loadImagesSynchronously(packageContext)
- logger.logAsyncTaskProgress(entry.logKey, "getting row image resolver (on wrong thread!)")
+ logger.logAsyncTaskProgress(
+ entry.logKey,
+ "getting row image resolver (on wrong thread!)",
+ )
val imageResolver = row.imageResolver
// wait for image resolver to finish preloading
logger.logAsyncTaskProgress(entry.logKey, "waiting for preloaded images")
@@ -685,13 +686,17 @@ constructor(
logger: NotificationRowContentBinderLogger,
): InflationProgress {
val rowImageInflater =
- RowImageInflater.newInstance(previousIndex = row.mImageModelIndex)
+ RowImageInflater.newInstance(
+ previousIndex = row.mImageModelIndex,
+ // inflating the contracted view is the legacy invalidation trigger
+ reinflating = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0,
+ )
val promotedContent =
if (PromotedNotificationContentModel.featureFlagEnabled()) {
logger.logAsyncTaskProgress(
entry.logKey,
- "extracting promoted notification content"
+ "extracting promoted notification content",
)
val imageModelProvider = rowImageInflater.useForContentModel()
promotedNotificationContentExtractor
@@ -750,9 +755,9 @@ constructor(
) {
logger.logAsyncTaskProgress(
entry.logKey,
- "inflating public single line view model"
+ "inflating public single line view model",
)
- if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ if (bindParams.redactionType == REDACTION_TYPE_OTP) {
SingleLineViewInflater.inflateSingleLineViewModel(
notification = entry.sbn.notification,
messagingStyle = messagingStyle,
@@ -852,18 +857,12 @@ constructor(
} else null
val expanded =
if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
- logger.logAsyncTaskProgress(
- row.loggingKey,
- "creating expanded remote view",
- )
+ logger.logAsyncTaskProgress(row.loggingKey, "creating expanded remote view")
createExpandedView(builder, bindParams.isMinimized)
} else null
val headsUp =
if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
- logger.logAsyncTaskProgress(
- row.loggingKey,
- "creating heads up remote view",
- )
+ logger.logAsyncTaskProgress(row.loggingKey, "creating heads up remote view")
val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle()
if (isHeadsUpCompact) {
builder.createCompactHeadsUpContentView()
@@ -873,13 +872,10 @@ constructor(
} else null
val public =
if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) {
- logger.logAsyncTaskProgress(
- row.loggingKey,
- "creating public remote view"
- )
+ logger.logAsyncTaskProgress(row.loggingKey, "creating public remote view")
if (
LockscreenOtpRedaction.isEnabled &&
- bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT
+ bindParams.redactionType == REDACTION_TYPE_OTP
) {
createSensitiveContentMessageNotification(
entry.sbn.notification,
@@ -1142,7 +1138,7 @@ constructor(
override fun setResultView(v: View) {
logger.logAsyncTaskProgress(
entry.logKey,
- "group header view applied"
+ "group header view applied",
)
result.inflatedGroupHeaderView = v as NotificationHeaderView?
}
@@ -1198,7 +1194,7 @@ constructor(
}
logger.logAsyncTaskProgress(
entry.logKey,
- "applying low priority group header view"
+ "applying low priority group header view",
)
applyRemoteView(
inflationExecutor = inflationExecutor,
@@ -1295,6 +1291,7 @@ constructor(
runningInflations,
e,
row,
+ entry,
callback,
logger,
"applying view synchronously",
@@ -1320,6 +1317,7 @@ constructor(
runningInflations,
InflationException(invalidReason),
row,
+ entry,
callback,
logger,
"applied invalid view",
@@ -1379,6 +1377,7 @@ constructor(
runningInflations,
e,
row,
+ entry,
callback,
logger,
"applying view",
@@ -1482,6 +1481,7 @@ constructor(
runningInflations: HashMap<Int, CancellationSignal>,
e: Exception,
notification: ExpandableNotificationRow?,
+ entry: NotificationEntry,
callback: InflationCallback?,
logger: NotificationRowContentBinderLogger,
logContext: String,
@@ -1489,7 +1489,7 @@ constructor(
Assert.isMainThread()
logger.logAsyncTaskException(notification?.loggingKey, logContext, e)
runningInflations.values.forEach(Consumer { obj: CancellationSignal -> obj.cancel() })
- callback?.handleInflationException(notification?.entry, e)
+ callback?.handleInflationException(entry, e)
}
/**
@@ -1522,10 +1522,6 @@ constructor(
entry.promotedNotificationContentModel = result.promotedContent
}
- if (PromotedNotificationUiForceExpanded.isEnabled) {
- row.setPromotedOngoing(entry.isOngoingPromoted())
- }
-
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
setContentViewsFromRemoteViews(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 83897f5bc3a7..cec0ae696b26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -51,7 +51,6 @@ import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Flags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.res.R;
@@ -477,7 +476,7 @@ public class NotificationSnooze extends LinearLayout
@Override
public boolean handleCloseControls(boolean save, boolean force) {
- if (Flags.notificationUndoGutsOnConfigChanged() && !save) {
+ if (!save) {
// Undo changes and let the guts handle closing the view
mSelectedOption = null;
showSnoozeOptions(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index 6ff711deeb01..01ee788f7fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
@@ -31,6 +31,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
@@ -42,6 +43,7 @@ import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyl
public class PromotedNotificationInfo extends NotificationInfo {
private static final String TAG = "PromotedNotifInfoGuts";
private INotificationManager mNotificationManager;
+ private PackageDemotionInteractor mPackageDemotionInteractor;
private NotificationGuts mGutsContainer;
public PromotedNotificationInfo(Context context, AttributeSet attrs) {
@@ -56,6 +58,7 @@ public class PromotedNotificationInfo extends NotificationInfo {
NotificationIconStyleProvider iconStyleProvider,
OnUserInteractionCallback onUserInteractionCallback,
ChannelEditorDialogController channelEditorDialogController,
+ PackageDemotionInteractor packageDemotionInteractor,
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
@@ -65,16 +68,19 @@ public class PromotedNotificationInfo extends NotificationInfo {
UiEventLogger uiEventLogger,
boolean isDeviceProvisioned,
boolean isNonblockable,
+ boolean isDismissable,
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException {
super.bindNotification(pm, iNotificationManager, appIconProvider, iconStyleProvider,
- onUserInteractionCallback, channelEditorDialogController, pkg, notificationChannel,
+ onUserInteractionCallback, channelEditorDialogController, packageDemotionInteractor,
+ pkg, notificationChannel,
entry, onSettingsClick, onAppSettingsClick, feedbackClickListener, uiEventLogger,
- isDeviceProvisioned, isNonblockable, wasShownHighPriority,
+ isDeviceProvisioned, isNonblockable, isDismissable, wasShownHighPriority,
assistantFeedbackController, metricsLogger, onCloseClick);
mNotificationManager = iNotificationManager;
+ mPackageDemotionInteractor = packageDemotionInteractor;
bindDemote(entry.getSbn(), pkg);
}
@@ -94,8 +100,8 @@ public class PromotedNotificationInfo extends NotificationInfo {
private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) {
return ((View v) -> {
try {
- // TODO(b/391661009): Signal AutomaticPromotionCoordinator here
mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true);
+ mPackageDemotionInteractor.onPackageDemoted(packageName, sbn.getUid());
mGutsContainer.closeControls(v, true);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't revoke live update permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt
index 95ef60fdcefe..7bac17f4c227 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt
@@ -83,9 +83,9 @@ interface RowImageInflater {
inline fun featureFlagEnabled() = PromotedNotificationUiAod.isEnabled
@JvmStatic
- fun newInstance(previousIndex: ImageModelIndex?): RowImageInflater =
+ fun newInstance(previousIndex: ImageModelIndex?, reinflating: Boolean): RowImageInflater =
if (featureFlagEnabled()) {
- RowImageInflaterImpl(previousIndex)
+ RowImageInflaterImpl(previousIndex, reinflating)
} else {
RowImageInflaterStub
}
@@ -110,7 +110,8 @@ private object RowImageInflaterStub : RowImageInflater {
override fun getNewImageIndex(): ImageModelIndex? = null
}
-class RowImageInflaterImpl(private val previousIndex: ImageModelIndex?) : RowImageInflater {
+class RowImageInflaterImpl(private val previousIndex: ImageModelIndex?, val reinflating: Boolean) :
+ RowImageInflater {
private val providedImages = mutableListOf<LazyImage>()
/**
@@ -139,10 +140,15 @@ class RowImageInflaterImpl(private val previousIndex: ImageModelIndex?) : RowIma
// ensure all entries are stored
providedImages.add(newImage)
// load the image result from the index into our new object
- previousIndex?.findImage(iconData, sizeClass, transform)?.let {
- // copy the result into our new object
- newImage.result = it
- }
+ previousIndex
+ // skip the cached image when we are "reinflating" to avoid stale content
+ // being displayed from the same URI after the app updated the notif
+ ?.takeUnless { reinflating }
+ ?.findImage(iconData, sizeClass, transform)
+ ?.let {
+ // copy the result into our new object
+ newImage.result = it
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 4146a941c025..99db1dba7e65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -53,8 +53,8 @@ import com.android.systemui.statusbar.notification.ImageTransformState;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
-import com.android.systemui.util.DimensionKt;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
+import com.android.systemui.util.DimensionKt;
import java.util.function.Consumer;
@@ -211,14 +211,28 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
rightIconLP.setMarginEnd(horizontalMargin);
mRightIcon.setLayoutParams(rightIconLP);
+ // if there is no title and topline view, there is nothing to adjust.
+ if (mNotificationTopLine == null && mTitle == null) {
+ return;
+ }
+
// align top line view to start of the right icon.
final int iconSize = mView.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_right_icon_size);
final int marginEnd = 2 * horizontalMargin + iconSize;
- mNotificationTopLine.setHeaderTextMarginEnd(marginEnd);
+ final boolean isTitleInTopLine;
+ // set margin end for the top line view if it exists
+ if (mNotificationTopLine != null) {
+ mNotificationTopLine.setHeaderTextMarginEnd(marginEnd);
+ isTitleInTopLine = mNotificationTopLine.isTitlePresent();
+ } else {
+ isTitleInTopLine = false;
+ }
+ // Margin is to be applied to the title only when it is in the body,
+ // but not in the title.
// title has too much margin on the right, so we need to reduce it
- if (mTitle != null) {
+ if (!isTitleInTopLine && mTitle != null) {
final ViewGroup.MarginLayoutParams titleLP =
(ViewGroup.MarginLayoutParams) mTitle.getLayoutParams();
titleLP.setMarginEnd(marginEnd);
@@ -397,7 +411,8 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
@Override
public int getExtraMeasureHeight() {
int extra = 0;
- if (mActions != null) {
+ if (!notificationsRedesignTemplates() && mActions != null) {
+ // With the redesign, this should always be 0.
extra = mActions.getExtraMeasureHeight();
}
if (mRemoteInputHistory != null && mRemoteInputHistory.getVisibility() != View.GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index f00c3ae20e30..53728c7da62d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -89,7 +89,8 @@ data class ActiveNotificationModel(
init {
if (!PromotedNotificationContentModel.featureFlagEnabled()) {
if (promotedContent != null) {
- Log.wtf(TAG, "passing non-null promoted content without feature flag enabled")
+ // TODO(b/401018545): convert to Log.wtf and fix tests (see: ag/32114199)
+ Log.e(TAG, "passing non-null promoted content without feature flag enabled")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt
index 72372319009c..37212a387fad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt
@@ -49,7 +49,9 @@ object NotificationBundleUi {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
@@ -57,4 +59,4 @@ object NotificationBundleUi {
*/
@JvmStatic
inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-} \ No newline at end of file
+}
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 1e249520e8b3..abfb86244390 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
@@ -36,13 +36,14 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
+import com.android.systemui.statusbar.notification.headsup.AvalancheController;
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import java.io.PrintWriter;
@@ -424,6 +425,7 @@ public class AmbientState implements Dumpable {
/** the bottom-most y position where we can draw pinned HUNs */
public float getHeadsUpBottom() {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+ NotificationsHunSharedAnimationValues.assertInLegacyMode();
return mHeadsUpBottom;
}
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 e830d18b7d73..315d37e55bc3 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
@@ -39,9 +39,11 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.compose.ui.platform.ComposeView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.NotificationExpandButton;
+import com.android.systemui.notifications.ui.composable.row.BundleHeaderKt;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -58,6 +60,7 @@ import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
@@ -119,6 +122,13 @@ public class NotificationChildrenContainer extends ViewGroup
*/
private boolean mEnableShadowOnChildNotifications;
+ /**
+ * This view is only set when this NCC is a bundle. If this view is set, all other header
+ * view variants have to be null.
+ */
+ private ComposeView mBundleHeaderView;
+ private BundleHeaderViewModelImpl mBundleHeaderViewModel;
+
private NotificationHeaderView mGroupHeader;
private NotificationHeaderViewWrapper mGroupHeaderWrapper;
private NotificationHeaderView mMinimizedGroupHeader;
@@ -189,6 +199,9 @@ public class NotificationChildrenContainer extends ViewGroup
R.dimen.notification_children_container_top_padding);
mHeaderHeight = mCollapsedHeaderMargin + mAdditionalExpandedHeaderMargin;
}
+ if (mBundleHeaderView != null) {
+ initBundleDimens();
+ }
mCollapsedBottomPadding = res.getDimensionPixelOffset(
R.dimen.notification_children_collapsed_bottom_padding);
mEnableShadowOnChildNotifications =
@@ -244,6 +257,10 @@ public class NotificationChildrenContainer extends ViewGroup
mMinimizedGroupHeader.getMeasuredWidth(),
mMinimizedGroupHeader.getMeasuredHeight());
}
+ if (mBundleHeaderView != null) {
+ mBundleHeaderView.layout(0, 0, mBundleHeaderView.getMeasuredWidth(),
+ mBundleHeaderView.getMeasuredHeight());
+ }
}
@Override
@@ -295,6 +312,9 @@ public class NotificationChildrenContainer extends ViewGroup
if (mMinimizedGroupHeader != null) {
mMinimizedGroupHeader.measure(widthMeasureSpec, headerHeightSpec);
}
+ if (mBundleHeaderView != null) {
+ mBundleHeaderView.measure(widthMeasureSpec, headerHeightSpec);
+ }
setMeasuredDimension(width, height);
Trace.endSection();
@@ -489,6 +509,28 @@ public class NotificationChildrenContainer extends ViewGroup
}
/**
+ * Init the bundle header view. The ComposeView is initialized within with the passed viewModel.
+ * This can only be init once and not in conjunction with any other header view.
+ */
+ public void initBundleHeader(@NonNull BundleHeaderViewModelImpl viewModel) {
+ if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) return;
+ if (mBundleHeaderView != null) return;
+ initBundleDimens();
+
+ mBundleHeaderViewModel = viewModel;
+ mBundleHeaderView = BundleHeaderKt.createComposeView(mBundleHeaderViewModel, getContext());
+ addView(mBundleHeaderView);
+ invalidate();
+ }
+
+ private void initBundleDimens() {
+ NotificationBundleUi.unsafeAssertInNewMode();
+ mCollapsedHeaderMargin = mHeaderHeight;
+ mAdditionalExpandedHeaderMargin = 0;
+ mCollapsedBottomPadding = 0;
+ }
+
+ /**
* Set the group header view
* @param headerView view to set
* @param onClickListener OnClickListener of the header view
@@ -1311,6 +1353,17 @@ public class NotificationChildrenContainer extends ViewGroup
mGroupHeader.setHeaderBackgroundDrawable(null);
}
}
+ if (mBundleHeaderView != null) {
+ if (expanded) {
+ ColorDrawable cd = new ColorDrawable();
+ cd.setColor(mContainingNotification.calculateBgColor());
+ // TODO(b/389839492): The backgroundDrawable needs an outline like in the original:
+ // setOutlineProvider(mProvider);
+ mBundleHeaderViewModel.setBackgroundDrawable(cd);
+ } else {
+ mBundleHeaderViewModel.setBackgroundDrawable(null);
+ }
+ }
}
public int getMaxContentHeight() {
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 1a17b8efb4ae..a5f711050c46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -104,6 +104,7 @@ import com.android.systemui.shade.QSHeaderBoundsProvider;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -117,6 +118,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimationEvent;
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
@@ -139,6 +141,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScr
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy;
import com.android.systemui.util.Assert;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
@@ -351,10 +354,11 @@ public class NotificationStackScrollLayout
private final int[] mTempInt2 = new int[2];
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
- private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
- = new HashSet<>();
+ private final Map<ExpandableNotificationRow, HeadsUpAnimationEvent> mHeadsUpChangeAnimations
+ = new HashMap<>();
private boolean mForceNoOverlappingRendering;
- private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
+ private final ArrayList<ExpandableNotificationRow> mTmpHeadsUpChangeAnimations =
+ new ArrayList<>();
private boolean mAnimationRunning;
private final ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@@ -673,7 +677,7 @@ public class NotificationStackScrollLayout
mExpandHelper.setScrollAdapter(mScrollAdapter);
if (NotificationsHunSharedAnimationValues.isEnabled()) {
- mHeadsUpAnimator = new HeadsUpAnimator(context);
+ mHeadsUpAnimator = new HeadsUpAnimator(context, /* systemBarUtilsProxy= */ null);
} else {
mHeadsUpAnimator = null;
}
@@ -1286,7 +1290,9 @@ public class NotificationStackScrollLayout
@Override
public void setHeadsUpBottom(float headsUpBottom) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- if (mAmbientState.getHeadsUpBottom() != headsUpBottom) {
+ if (NotificationsHunSharedAnimationValues.isEnabled()) {
+ mHeadsUpAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
+ } else if (mAmbientState.getHeadsUpBottom() != headsUpBottom) {
mAmbientState.setHeadsUpBottom(headsUpBottom);
mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
}
@@ -3074,20 +3080,20 @@ public class NotificationStackScrollLayout
*/
private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
boolean hasAddEvent = false;
- for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
- ExpandableNotificationRow row = eventPair.first;
- boolean isHeadsUp = eventPair.second;
+ for (HeadsUpAnimationEvent event : mHeadsUpChangeAnimations.values()) {
+ ExpandableNotificationRow row = event.getRow();
+ boolean isHeadsUp = event.isHeadsUpAppearance();
if (child == row) {
- mTmpList.add(eventPair);
+ mTmpHeadsUpChangeAnimations.add(event.getRow());
hasAddEvent |= isHeadsUp;
}
}
if (hasAddEvent) {
// This child was just added lets remove all events.
- mHeadsUpChangeAnimations.removeAll(mTmpList);
+ mTmpHeadsUpChangeAnimations.forEach((row) -> mHeadsUpChangeAnimations.remove(row));
((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
}
- mTmpList.clear();
+ mTmpHeadsUpChangeAnimations.clear();
return hasAddEvent && mAddedHeadsUpChildren.contains(child);
}
@@ -3373,9 +3379,9 @@ public class NotificationStackScrollLayout
}
private void generateHeadsUpAnimationEvents() {
- for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
- ExpandableNotificationRow row = eventPair.first;
- boolean isHeadsUp = eventPair.second;
+ for (HeadsUpAnimationEvent headsUpEvent : mHeadsUpChangeAnimations.values()) {
+ ExpandableNotificationRow row = headsUpEvent.getRow();
+ boolean isHeadsUp = headsUpEvent.isHeadsUpAppearance();
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
@@ -3433,6 +3439,10 @@ public class NotificationStackScrollLayout
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
+
+ boolean hasStatusBarChip =
+ StatusBarNotifChips.isEnabled() && headsUpEvent.getHasStatusBarChip();
+ event.headsUpHasStatusBarChip = hasStatusBarChip;
// TODO(b/283084712) remove this and update the HUN filters at creation
event.filter.animateHeight = false;
mAnimationEvents.add(event);
@@ -3844,8 +3854,6 @@ public class NotificationStackScrollLayout
// existing overScroll, we have to scroll the view
customOverScrollBy((int) scrollAmount, getOwnScrollY(),
range, getHeight() / 2);
- // If we're scrolling, leavebehinds should be dismissed
- mController.checkSnoozeLeavebehind();
}
}
break;
@@ -5068,10 +5076,11 @@ public class NotificationStackScrollLayout
mAnimationFinishedRunnables.add(runnable);
}
- public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(
+ NotificationEntry entry, boolean isHeadsUp, boolean hasStatusBarChip) {
SceneContainerFlag.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
- generateHeadsUpAnimation(row, isHeadsUp);
+ generateHeadsUpAnimation(row, isHeadsUp, hasStatusBarChip);
}
/**
@@ -5080,8 +5089,11 @@ public class NotificationStackScrollLayout
*
* @param row to animate
* @param isHeadsUp true for appear, false for disappear animations
+ * @param hasStatusBarChip true if the status bar is currently displaying a chip for the given
+ * notification
*/
- public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(
+ ExpandableNotificationRow row, boolean isHeadsUp, boolean hasStatusBarChip) {
boolean addAnimation =
mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
if (NotificationThrottleHun.isEnabled()) {
@@ -5096,19 +5108,26 @@ public class NotificationStackScrollLayout
: " isSeenInShade=" + row.getEntry().isSeenInShade()
+ " row=" + row.getKey())
+ " mIsExpanded=" + mIsExpanded
- + " isHeadsUp=" + isHeadsUp);
+ + " isHeadsUp=" + isHeadsUp
+ + " hasStatusBarChip=" + hasStatusBarChip);
}
+
if (addAnimation) {
// If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
// and do not add the disappear event either.
- if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
+ boolean showingHunThisFrame =
+ mHeadsUpChangeAnimations.containsKey(row)
+ && mHeadsUpChangeAnimations.get(row).isHeadsUpAppearance();
+ if (!isHeadsUp && showingHunThisFrame) {
+ mHeadsUpChangeAnimations.remove(row);
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
logHunAnimationSkipped(row, "previous hun appear animation cancelled");
return;
}
- mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
+ mHeadsUpChangeAnimations.put(
+ row, new HeadsUpAnimationEvent(row, isHeadsUp, hasStatusBarChip));
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
@@ -5116,6 +5135,9 @@ public class NotificationStackScrollLayout
setHeadsUpAnimatingAway(true);
}
}
+ if (StatusBarNotifChips.isEnabled()) {
+ row.setHasStatusBarChipDuringHeadsUpAnimation(hasStatusBarChip);
+ }
requestChildrenUpdate();
}
}
@@ -6469,30 +6491,50 @@ public class NotificationStackScrollLayout
static AnimationFilter[] FILTERS = new AnimationFilter[]{
// ANIMATION_TYPE_ADD
- new AnimationFilter()
- .animateAlpha()
- .animateHeight()
- .animateTopInset()
- .animateY()
- .animateZ()
- .hasDelays(),
+ physicalNotificationMovement()
+ ? new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ : new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
// ANIMATION_TYPE_REMOVE
- new AnimationFilter()
- .animateAlpha()
- .animateHeight()
- .animateTopInset()
- .animateY()
- .animateZ()
- .hasDelays(),
+ physicalNotificationMovement()
+ ? new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ : new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
// ANIMATION_TYPE_REMOVE_SWIPED_OUT
- new AnimationFilter()
- .animateHeight()
- .animateTopInset()
- .animateY()
- .animateZ()
- .hasDelays(),
+ physicalNotificationMovement()
+ ? new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ : new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
// ANIMATION_TYPE_TOP_PADDING_CHANGED
new AnimationFilter()
@@ -6682,6 +6724,7 @@ public class NotificationStackScrollLayout
final long length;
View viewAfterChangingView;
boolean headsUpFromBottom;
+ boolean headsUpHasStatusBarChip;
AnimationEvent(ExpandableView view, int type) {
this(view, type, LENGTHS[type]);
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 124e6f590bfe..bb3abc1fba38 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
@@ -92,6 +92,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
@@ -325,6 +326,14 @@ public class NotificationStackScrollLayoutController implements Dumpable {
*/
private float mMaxAlphaForGlanceableHub = 1.0f;
+ /**
+ * A list of keys for the visible status bar chips.
+ *
+ * Note that this list can contain both notification keys, as well as keys for other types of
+ * chips like screen recording.
+ */
+ private List<String> mVisibleStatusBarChipKeys = new ArrayList<>();
+
private final NotificationListViewBinder mViewBinder;
private void updateResources() {
@@ -1580,8 +1589,16 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView.getFirstChildNotGone();
}
+ /** Sets the list of keys that have currently visible status bar chips. */
+ public void updateStatusBarChipKeys(List<String> visibleStatusBarChipKeys) {
+ mVisibleStatusBarChipKeys = visibleStatusBarChipKeys;
+ }
+
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
- mView.generateHeadsUpAnimation(entry, isHeadsUp);
+ boolean hasStatusBarChip =
+ StatusBarNotifChips.isEnabled()
+ && mVisibleStatusBarChipKeys.contains(entry.getKey());
+ mView.generateHeadsUpAnimation(entry, isHeadsUp, hasStatusBarChip);
}
public void setMaxTopPadding(int padding) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 4e916804318d..fcb63df1a528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -409,12 +409,12 @@ constructor(
if (counter != null) {
if (NotificationBundleUi.isEnabled) {
- val entry = (currentNotification as? ExpandableNotificationRow)?.entry
- counter.incrementForBucket(entry?.bucket)
- } else {
val entryAdapter =
(currentNotification as? ExpandableNotificationRow)?.entryAdapter
counter.incrementForBucket(entryAdapter?.sectionBucket)
+ } else {
+ val entry = (currentNotification as? ExpandableNotificationRow)?.entry
+ counter.incrementForBucket(entry?.bucket)
}
}
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 4effb76c6570..28218227506c 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
@@ -967,9 +967,12 @@ public class StackScrollAlgorithm {
childState.setZTranslation(baseZ);
}
if (isTopEntry && row.isAboveShelf()) {
+ float headsUpBottom = NotificationsHunSharedAnimationValues.isEnabled()
+ ? mHeadsUpAnimator.getHeadsUpAppearHeightBottom()
+ : ambientState.getHeadsUpBottom();
clampHunToMaxTranslation(
/* headsUpTop = */ headsUpTranslation,
- /* headsUpBottom = */ ambientState.getHeadsUpBottom(),
+ /* headsUpBottom = */ headsUpBottom,
/* viewState = */ childState
);
updateCornerRoundnessForPinnedHun(row, ambientState.getStackTop());
@@ -1059,7 +1062,9 @@ public class StackScrollAlgorithm {
shouldHunAppearFromBottom(ambientState, childState);
if (NotificationsHunSharedAnimationValues.isEnabled()) {
int yTranslation =
- mHeadsUpAnimator.getHeadsUpYTranslation(shouldHunAppearFromBottom);
+ mHeadsUpAnimator.getHeadsUpYTranslation(
+ shouldHunAppearFromBottom,
+ row.hasStatusBarChipDuringHeadsUpAnimation());
childState.setYTranslation(yTranslation);
} else {
if (shouldHunAppearFromBottom) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 19abfa8140df..5414318b29bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -38,6 +38,7 @@ import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
@@ -289,6 +290,10 @@ public class StackStateAnimator {
long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
switch (event.animationType) {
case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
+ if (physicalNotificationMovement()) {
+ // We don't want any delays when adding anymore
+ continue;
+ }
int ownIndex = viewState.notGoneIndex;
int changingIndex =
((ExpandableView) (event.mChangingView)).getViewState().notGoneIndex;
@@ -302,6 +307,10 @@ public class StackStateAnimator {
case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
+ if (physicalNotificationMovement()) {
+ // We don't want any delays when removing anymore
+ continue;
+ }
int ownIndex = viewState.notGoneIndex;
boolean noNextView = event.viewAfterChangingView == null;
ExpandableView viewAfterChangingView = noNextView
@@ -552,7 +561,9 @@ public class StackStateAnimator {
mHeadsUpAppearChildren.add(changingView);
mTmpState.copyFrom(changingView.getViewState());
- mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(
+ event.headsUpFromBottom, event.headsUpHasStatusBarChip));
// set the height and the initial position
mTmpState.applyToView(changingView);
mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
@@ -664,7 +675,9 @@ public class StackStateAnimator {
// StackScrollAlgorithm cannot find this view because it has been removed
// from the NSSL. To correctly translate the view to the top or bottom of
// the screen (where it animated from), we need to update its translation.
- mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(
+ event.headsUpFromBottom, event.headsUpHasStatusBarChip));
endRunnable = changingView::removeFromTransientContainer;
}
@@ -735,9 +748,9 @@ public class StackStateAnimator {
return needsCustomAnimation;
}
- private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) {
+ private float getHeadsUpYTranslationStart(boolean headsUpFromBottom, boolean hasStatusBarChip) {
if (NotificationsHunSharedAnimationValues.isEnabled()) {
- return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom);
+ return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom, hasStatusBarChip);
}
if (headsUpFromBottom) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 10b665d8ef01..facb8941f1fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -33,6 +33,7 @@ import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.dagger.SilentHeader
@@ -133,6 +134,14 @@ constructor(
}
}
+ if (StatusBarNotifChips.isEnabled) {
+ launch {
+ viewModel.visibleStatusBarChipKeys.collect { keys ->
+ viewController.updateStatusBarChipKeys(keys)
+ }
+ }
+ }
+
launch { bindLogger(view) }
}
}
@@ -231,7 +240,7 @@ constructor(
emptyShadeViewModel: EmptyShadeViewModel,
parentView: NotificationStackScrollLayout,
) {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
// The empty shade needs to be re-inflated every time the theme or the font size
// changes.
configuration
@@ -269,7 +278,7 @@ constructor(
emptyShadeView: EmptyShadeView,
emptyShadeViewModel: EmptyShadeViewModel,
): Unit = coroutineScope {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
launch {
emptyShadeView.repeatWhenAttachedToWindow {
EmptyShadeViewBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5ed1889de01e..c1eb70ed7d25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -20,6 +20,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -56,6 +57,7 @@ class NotificationListViewModel
constructor(
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
+ val ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
val footerViewModelFactory: FooterViewModel.Factory,
val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory,
val logger: Optional<NotificationLoggerViewModel>,
@@ -364,6 +366,14 @@ constructor(
}
}
+ /**
+ * A list of keys for the visible status bar chips.
+ *
+ * Note that this list can contain both notification keys, as well as keys for other types of
+ * chips like screen recording.
+ */
+ val visibleStatusBarChipKeys = ongoingActivityChipsViewModel.visibleChipKeys
+
// TODO(b/325936094) use it for the text displayed in the StatusBar
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowViewModel =
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index a277597e23df..c1aa5f12aa99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -266,7 +266,7 @@ constructor(
combine(shadeModeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion
->
when (shadeMode) {
- is ShadeMode.Dual -> false
+ is ShadeMode.Dual,
is ShadeMode.Split -> true
is ShadeMode.Single -> qsExpansion < 0.5f
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index feb74098f071..bc533148f514 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -27,20 +29,29 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
class HeadsUpNotificationViewBinder
@Inject
-constructor(private val viewModel: NotificationListViewModel) {
+constructor(
+ private val viewModel: NotificationListViewModel,
+ private val ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
+) {
suspend fun bindHeadsUpNotifications(parentView: NotificationStackScrollLayout): Unit =
coroutineScope {
launch {
var previousKeys = emptySet<HeadsUpRowKey>()
- combine(viewModel.pinnedHeadsUpRowKeys, viewModel.activeHeadsUpRowKeys, ::Pair)
+ combine(
+ viewModel.pinnedHeadsUpRowKeys,
+ viewModel.activeHeadsUpRowKeys,
+ ongoingActivityChipsViewModel.visibleChipKeys,
+ ::Triple,
+ )
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
val pinned = newKeys.first
val all = newKeys.second
+ val statusBarChips: List<String> = newKeys.third
+
val added = all.union(pinned) - previousKeys
val removed = previousKeys - pinned
previousKeys = pinned
@@ -48,15 +59,23 @@ constructor(private val viewModel: NotificationListViewModel) {
if (animationsEnabled) {
added.forEach { key ->
+ val row = obtainView(key)
+ val hasStatusBarChip = statusBarChips.contains(row.entry.key)
parentView.generateHeadsUpAnimation(
- obtainView(key),
+ row,
/* isHeadsUp = */ true,
+ hasStatusBarChip,
)
}
removed.forEach { key ->
val row = obtainView(key)
+ val hasStatusBarChip = statusBarChips.contains(row.entry.key)
if (!parentView.isBeingDragged()) {
- parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+ parentView.generateHeadsUpAnimation(
+ row,
+ /* isHeadsUp= */ false,
+ hasStatusBarChip,
+ )
}
row.markHeadsUpSeen()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
index 2ae38dd488bd..616bab60e02f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
@@ -22,9 +22,9 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -41,10 +41,13 @@ constructor(
private val autoHideControllerFactory: AutoHideControllerImpl.Factory,
) :
AutoHideControllerStore,
- PerDisplayStoreImpl<AutoHideController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<AutoHideController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): AutoHideController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 9aa4c54c4292..e4e56c5de65b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -58,10 +58,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.model.Scenes;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
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 face1c7512ce..a4ee4ad6f6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -109,6 +109,7 @@ import com.android.systemui.AutoReinflateContainer;
import com.android.systemui.CoreStartable;
import com.android.systemui.DejankUtils;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Flags;
import com.android.systemui.InitController;
import com.android.systemui.Prefs;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
@@ -139,6 +140,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.notetask.NoteTaskController;
@@ -190,7 +192,6 @@ import com.android.systemui.statusbar.LiftReveal;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -3183,12 +3184,27 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
new ActivityTransitionAnimator.Listener() {
@Override
public void onTransitionAnimationStart() {
- mKeyguardViewMediator.setBlursDisabledForAppLaunch(true);
+ if (!Flags.notificationShadeBlur() || !Flags.moveTransitionAnimationLayer()) {
+ mKeyguardViewMediator.setBlursDisabledForAppLaunch(true);
+ }
+ }
+
+ @Override
+ public void onTransitionAnimationProgress(float linearProgress) {
+ if (Flags.notificationShadeBlur() && Flags.moveTransitionAnimationLayer()) {
+ mNotificationShadeDepthControllerLazy.get()
+ .onTransitionAnimationProgress(linearProgress);
+ }
}
@Override
public void onTransitionAnimationEnd() {
- mKeyguardViewMediator.setBlursDisabledForAppLaunch(false);
+ if (Flags.notificationShadeBlur() && Flags.moveTransitionAnimationLayer()) {
+ mNotificationShadeDepthControllerLazy.get()
+ .onTransitionAnimationEnd();
+ } else {
+ mKeyguardViewMediator.setBlursDisabledForAppLaunch(false);
+ }
}
};
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 74b1c3bbfd77..2c8866fef030 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -38,6 +38,7 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.InitController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -50,7 +51,6 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
index fbc6b9524a6d..372e91f88ae5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.phone
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 323b7d8eaaeb..ba5570026c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.qualifiers.Default
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.CommandQueueInitializer
import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore
+import com.android.systemui.statusbar.core.MultiDisplayStatusBarOrchestratorStore
import com.android.systemui.statusbar.core.MultiDisplayStatusBarStarter
import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -197,5 +198,19 @@ interface StatusBarPhoneModule {
CoreStartable.NOP
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(MultiDisplayStatusBarOrchestratorStore::class)
+ fun orchestratorStoreAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarOrchestratorStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index c541cff4448c..d8d6979e1379 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -875,7 +875,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void showSecondaryOngoingActivityChip(boolean animate) {
- StatusBarNotifChips.assertInNewMode();
+ StatusBarNotifChips.unsafeAssertInNewMode();
StatusBarRootModernization.assertInLegacyMode();
animateShow(mSecondaryOngoingActivityChip, animate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index b6628926dc4b..61b7d80a8c85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -25,7 +25,6 @@ import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
-import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -162,6 +161,10 @@ constructor(
notificationKey = currentInfo.key,
appName = currentInfo.appName,
promotedContent = currentInfo.promotedContent,
+ // [hasOngoingCall()] filters out the case in which the call is ongoing but the app
+ // is visible (we issue [OngoingCallModel.NoCall] below in that case), so this can
+ // be safely made false.
+ isAppVisible = false,
)
} else {
return OngoingCallModel.NoCall
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
index b77e8f2ffefc..0ac87178086f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
@@ -52,7 +52,9 @@ object StatusBarChipsModernization {
* the flag is not enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
index d6ca656356e3..bed9e7cf4646 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -51,9 +51,7 @@ import kotlinx.coroutines.flow.stateIn
* This class monitors call notifications and the visibility of call apps to determine the
* appropriate chip state. It emits:
* * - [OngoingCallModel.NoCall] when there is no call notification
- * * - [OngoingCallModel.InCallWithVisibleApp] when there is a call notification but the call app is
- * visible
- * * - [OngoingCallModel.InCall] when there is a call notification and the call app is not visible
+ * * - [OngoingCallModel.InCall] when there is a call notification
*/
@SysUISingleton
class OngoingCallInteractor
@@ -85,12 +83,14 @@ constructor(
initialValue = OngoingCallModel.NoCall,
)
+ // TODO(b/400720280): maybe put this inside [OngoingCallModel].
@VisibleForTesting
val isStatusBarRequiredForOngoingCall =
combine(ongoingCallState, isChipSwipedAway) { callState, chipSwipedAway ->
- callState is OngoingCallModel.InCall && !chipSwipedAway
+ callState.willCallChipBeVisible() && !chipSwipedAway
}
+ // TODO(b/400720280): maybe put this inside [OngoingCallModel].
@VisibleForTesting
val isGestureListeningEnabled =
combine(
@@ -98,9 +98,12 @@ constructor(
statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode,
isChipSwipedAway,
) { callState, isFullscreen, chipSwipedAway ->
- callState is OngoingCallModel.InCall && !chipSwipedAway && isFullscreen
+ callState.willCallChipBeVisible() && !chipSwipedAway && isFullscreen
}
+ private fun OngoingCallModel.willCallChipBeVisible() =
+ this is OngoingCallModel.InCall && !isAppVisible
+
private fun createOngoingCallStateFlow(
notification: ActiveNotificationModel?
): Flow<OngoingCallModel> {
@@ -147,34 +150,23 @@ constructor(
model: ActiveNotificationModel,
isVisible: Boolean,
): OngoingCallModel {
- return when {
- isVisible -> {
- logger.d({ "Call app is visible: uid=$int1" }) { int1 = model.uid }
- OngoingCallModel.InCallWithVisibleApp(
- startTimeMs = model.whenTime,
- notificationIconView = model.statusBarChipIconView,
- intent = model.contentIntent,
- notificationKey = model.key,
- appName = model.appName,
- promotedContent = model.promotedContent,
- )
- }
-
- else -> {
- logger.d({ "Active call detected: startTime=$long1 hasIcon=$bool1" }) {
- long1 = model.whenTime
- bool1 = model.statusBarChipIconView != null
- }
- OngoingCallModel.InCall(
- startTimeMs = model.whenTime,
- notificationIconView = model.statusBarChipIconView,
- intent = model.contentIntent,
- notificationKey = model.key,
- appName = model.appName,
- promotedContent = model.promotedContent,
- )
- }
+ logger.d({
+ "Active call detected: uid=$int1 startTime=$long1 hasIcon=$bool1 isAppVisible=$bool2"
+ }) {
+ int1 = model.uid
+ long1 = model.whenTime
+ bool1 = model.statusBarChipIconView != null
+ bool2 = isVisible
}
+ return OngoingCallModel.InCall(
+ startTimeMs = model.whenTime,
+ notificationIconView = model.statusBarChipIconView,
+ intent = model.contentIntent,
+ notificationKey = model.key,
+ appName = model.appName,
+ promotedContent = model.promotedContent,
+ isAppVisible = isVisible,
+ )
}
private fun setStatusBarRequiredForOngoingCall(statusBarRequired: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 62f0ba032f36..322dfff8f144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -26,25 +26,6 @@ sealed interface OngoingCallModel {
data object NoCall : OngoingCallModel
/**
- * There is an ongoing call but the call app is currently visible, so we don't need to show the
- * chip.
- *
- * @property startTimeMs see [InCall.startTimeMs].
- * @property notificationIconView see [InCall.notificationIconView].
- * @property intent see [InCall.intent].
- * @property appName see [InCall.appName].
- * @property promotedContent see [InCall.promotedContent].
- */
- data class InCallWithVisibleApp(
- val startTimeMs: Long,
- val notificationIconView: StatusBarIconView?,
- val intent: PendingIntent?,
- val notificationKey: String,
- val appName: String,
- val promotedContent: PromotedNotificationContentModel?,
- ) : OngoingCallModel
-
- /**
* There *is* an ongoing call.
*
* @property startTimeMs the time that the phone call started, based on the notification's
@@ -58,6 +39,7 @@ sealed interface OngoingCallModel {
* @property appName the user-readable name of the app that posted the call notification.
* @property promotedContent if the call notification also meets promoted notification criteria,
* this field is filled in with the content related to promotion. Otherwise null.
+ * @property isAppVisible whether the app to which the call belongs is currently visible.
*/
data class InCall(
val startTimeMs: Long,
@@ -66,5 +48,6 @@ sealed interface OngoingCallModel {
val notificationKey: String,
val appName: String,
val promotedContent: PromotedNotificationContentModel?,
+ val isAppVisible: Boolean,
) : OngoingCallModel
}
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
index f82e681de76f..e2fd4bdc45a9 100644
--- 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
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.airplane.data.repository
import android.net.ConnectivityManager
import android.os.Handler
import android.provider.Settings.Global
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
index d53cbabb1d19..342adc6af003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
@@ -65,12 +65,12 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
*/
val batteryAttributionType =
combine(isCharging, powerSave, isBatteryDefenderEnabled) { charging, powerSave, defend ->
- if (charging) {
- BatteryAttributionModel.Charging
- } else if (powerSave) {
+ if (powerSave) {
BatteryAttributionModel.PowerSave
} else if (defend) {
BatteryAttributionModel.Defend
+ } else if (charging) {
+ BatteryAttributionModel.Charging
} else {
null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt
index 732ea6ac6790..5127e8a14796 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/ui/composable/UnifiedBattery.kt
@@ -101,7 +101,13 @@ fun BatteryCanvas(
for (glyph in glyphs) {
// Move the glyph to the right spot
val verticalOffset = (BatteryFrame.innerHeight - glyph.height) / 2
- inset(horizontalOffset, verticalOffset) { glyph.draw(this, colors) }
+ inset(
+ // Never try and inset more than half of the available size - see b/400246091.
+ minOf(horizontalOffset, size.width / 2),
+ minOf(verticalOffset, size.height / 2),
+ ) {
+ glyph.draw(this, colors)
+ }
horizontalOffset += glyph.width + INTER_GLYPH_PADDING_PX
}
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 29528502aa03..db1977b3ff45 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
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.Mobil
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosAdapter
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosImpl
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
@@ -92,8 +93,10 @@ import kotlinx.coroutines.flow.Flow
DemoModeMobileConnectionDataSourceKairosImpl.Module::class,
MobileRepositorySwitcherKairos.Module::class,
MobileConnectionsRepositoryKairosImpl.Module::class,
+ MobileIconsInteractorKairosImpl.Module::class,
MobileConnectionRepositoryKairosFactoryImpl.Module::class,
MobileConnectionsRepositoryKairosAdapter.Module::class,
+ MobileIconsInteractorKairosAdapter.Module::class,
]
)
abstract class StatusBarPipelineModule {
@@ -171,7 +174,7 @@ abstract class StatusBarPipelineModule {
@Provides
fun mobileIconsInteractor(
impl: Provider<MobileIconsInteractorImpl>,
- kairosImpl: Provider<MobileIconsInteractorKairosImpl>,
+ kairosImpl: Provider<MobileIconsInteractorKairosAdapter>,
): MobileIconsInteractor {
return if (Flags.statusBarMobileIconKairos()) {
kairosImpl.get()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt
index 48747df23e0c..b2d314c3590b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt
@@ -50,7 +50,9 @@ object StatusBarSignalPolicyRefactorEthernet {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt
index 1f5b849c56cc..f4076ae34a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt
@@ -24,7 +24,7 @@ import com.android.systemui.Flags
import com.android.systemui.KairosActivatable
import com.android.systemui.KairosBuilder
import com.android.systemui.activated
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 2efc0579f333..d6105c2bd93f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -192,20 +192,16 @@ constructor(
override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
serviceStateChangedEvent
.mapLatest {
- val modems = telephonyManager.activeModemCount
-
- // Assume false for automotive devices which don't have the calling feature.
- // TODO: b/398045526 to revisit the below.
- val isAutomotive: Boolean =
- context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
- val hasFeatureCalling: Boolean =
+ // TODO(b/400460777): check for hasSystemFeature only once
+ val hasRadioAccess: Boolean =
context.packageManager.hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_CALLING
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS
)
- if (isAutomotive && !hasFeatureCalling) {
+ if (!hasRadioAccess) {
return@mapLatest false
}
+ val modems = telephonyManager.activeModemCount
// Check the service state for every modem. If any state reports emergency calling
// capable, then consider the device to have emergency call capabilities
(0..<modems)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
index 4580ad974b29..a9399593973b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,38 +22,37 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.map
+import com.android.systemui.kairosBuilder
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+@ExperimentalKairosApi
interface MobileIconInteractorKairos {
/** The table log created for this connection */
val tableLogBuffer: TableLogBuffer
/** The current mobile data activity */
- val activity: Flow<DataActivityModel>
+ val activity: State<DataActivityModel>
/** See [MobileConnectionsRepository.mobileIsDefault]. */
- val mobileIsDefault: Flow<Boolean>
+ val mobileIsDefault: State<Boolean>
/**
* True when telephony tells us that the data state is CONNECTED. See
@@ -61,31 +60,31 @@ interface MobileIconInteractorKairos {
* consider this connection to be serving data, and thus want to show a network type icon, when
* data is connected. Other data connection states would typically cause us not to show the icon
*/
- val isDataConnected: StateFlow<Boolean>
+ val isDataConnected: State<Boolean>
/** True if we consider this connection to be in service, i.e. can make calls */
- val isInService: StateFlow<Boolean>
+ val isInService: State<Boolean>
/** True if this connection is emergency only */
- val isEmergencyOnly: StateFlow<Boolean>
+ val isEmergencyOnly: State<Boolean>
/** Observable for the data enabled state of this connection */
- val isDataEnabled: StateFlow<Boolean>
+ val isDataEnabled: State<Boolean>
/** True if the RAT icon should always be displayed and false otherwise. */
- val alwaysShowDataRatIcon: StateFlow<Boolean>
+ val alwaysShowDataRatIcon: State<Boolean>
/** Canonical representation of the current mobile signal strength as a triangle. */
- val signalLevelIcon: StateFlow<SignalIconModel>
+ val signalLevelIcon: State<SignalIconModel>
/** Observable for RAT type (network type) indicator */
- val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+ val networkTypeIconGroup: State<NetworkTypeIconModel>
/** Whether or not to show the slice attribution */
- val showSliceAttribution: StateFlow<Boolean>
+ val showSliceAttribution: State<Boolean>
/** True if this connection is satellite-based */
- val isNonTerrestrial: StateFlow<Boolean>
+ val isNonTerrestrial: State<Boolean>
/**
* Provider name for this network connection. The name can be one of 3 values:
@@ -95,7 +94,7 @@ interface MobileIconInteractorKairos {
* override in [connectionInfo.operatorAlphaShort], a value that is derived from
* [ServiceState]
*/
- val networkName: StateFlow<NetworkNameModel>
+ val networkName: State<NetworkNameModel>
/**
* Provider name for this network connection. The name can be one of 3 values:
@@ -108,119 +107,110 @@ interface MobileIconInteractorKairos {
* TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
* provided is identical
*/
- val carrierName: StateFlow<String>
+ val carrierName: State<String>
/** True if there is only one active subscription. */
- val isSingleCarrier: StateFlow<Boolean>
+ val isSingleCarrier: State<Boolean>
/**
* True if this connection is considered roaming. The roaming bit can come from [ServiceState],
* or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
* connection to be roaming while carrier network change is active
*/
- val isRoaming: StateFlow<Boolean>
+ val isRoaming: State<Boolean>
/** See [MobileIconsInteractor.isForceHidden]. */
- val isForceHidden: Flow<Boolean>
+ val isForceHidden: State<Boolean>
/** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
- val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+ val isAllowedDuringAirplaneMode: State<Boolean>
/** True when in carrier network change mode */
- val carrierNetworkChangeActive: StateFlow<Boolean>
+ val carrierNetworkChangeActive: State<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@ExperimentalKairosApi
class MobileIconInteractorKairosImpl(
- @Background scope: CoroutineScope,
- defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
- override val alwaysShowDataRatIcon: StateFlow<Boolean>,
- alwaysUseCdmaLevel: StateFlow<Boolean>,
- override val isSingleCarrier: StateFlow<Boolean>,
- override val mobileIsDefault: StateFlow<Boolean>,
- defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
- defaultMobileIconGroup: StateFlow<MobileIconGroup>,
- isDefaultConnectionFailed: StateFlow<Boolean>,
- override val isForceHidden: Flow<Boolean>,
- connectionRepository: MobileConnectionRepository,
+ defaultSubscriptionHasDataEnabled: State<Boolean>,
+ override val alwaysShowDataRatIcon: State<Boolean>,
+ alwaysUseCdmaLevel: State<Boolean>,
+ override val isSingleCarrier: State<Boolean>,
+ override val mobileIsDefault: State<Boolean>,
+ defaultMobileIconMapping: State<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: State<MobileIconGroup>,
+ isDefaultConnectionFailed: State<Boolean>,
+ override val isForceHidden: State<Boolean>,
+ private val connectionRepository: MobileConnectionRepositoryKairos,
private val context: Context,
- val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl(),
-) : MobileIconInteractor, MobileIconInteractorKairos {
- override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
+ private val carrierIdOverrides: MobileIconCarrierIdOverrides =
+ MobileIconCarrierIdOverridesImpl(),
+) : MobileIconInteractorKairos, KairosBuilder by kairosBuilder() {
+ override val tableLogBuffer: TableLogBuffer
+ get() = connectionRepository.tableLogBuffer
- override val activity = connectionRepository.dataActivityDirection
+ override val activity: State<DataActivityModel>
+ get() = connectionRepository.dataActivityDirection
- override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+ override val isDataEnabled: State<Boolean> = connectionRepository.dataEnabled
- override val carrierNetworkChangeActive: StateFlow<Boolean> =
- connectionRepository.carrierNetworkChangeActive
+ override val carrierNetworkChangeActive: State<Boolean>
+ get() = connectionRepository.carrierNetworkChangeActive
// True if there exists _any_ icon override for this carrierId. Note that overrides can include
// any or none of the icon groups defined in MobileMappings, so we still need to check on a
// per-network-type basis whether or not the given icon group is overridden
- private val carrierIdIconOverrideExists =
- connectionRepository.carrierId
- .map { carrierIdOverrides.carrierIdEntryExists(it) }
- .distinctUntilChanged()
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ private val carrierIdIconOverrideExists: State<Boolean> =
+ connectionRepository.carrierId.map { carrierIdOverrides.carrierIdEntryExists(it) }
- override val networkName =
+ override val networkName: State<NetworkNameModel> =
combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
- operatorAlphaShort,
- networkName ->
- if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
- NetworkNameModel.IntentDerived(operatorAlphaShort)
- } else {
- networkName
- }
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ NetworkNameModel.IntentDerived(operatorAlphaShort)
+ } else {
+ networkName
}
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- connectionRepository.networkName.value,
- )
+ }
- override val carrierName =
+ override val carrierName: State<String> =
combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) {
- operatorAlphaShort,
- networkName ->
- if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
- operatorAlphaShort
- } else {
- networkName.name
- }
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ operatorAlphaShort
+ } else {
+ networkName.name
}
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- connectionRepository.carrierName.value.name,
- )
+ }
/** What the mobile icon would be before carrierId overrides */
- private val defaultNetworkType: StateFlow<MobileIconGroup> =
+ private val defaultNetworkType: State<MobileIconGroup> =
combine(
- connectionRepository.resolvedNetworkType,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- ) { resolvedNetworkType, mapping, defaultGroup ->
- when (resolvedNetworkType) {
- is ResolvedNetworkType.CarrierMergedNetworkType ->
- resolvedNetworkType.iconGroupOverride
- else -> {
- mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
- }
+ connectionRepository.resolvedNetworkType,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { resolvedNetworkType, mapping, defaultGroup ->
+ when (resolvedNetworkType) {
+ is ResolvedNetworkType.CarrierMergedNetworkType ->
+ resolvedNetworkType.iconGroupOverride
+
+ else -> {
+ mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+ }
- override val networkTypeIconGroup =
- combine(defaultNetworkType, carrierIdIconOverrideExists) { networkType, overrideExists ->
+ override val networkTypeIconGroup: State<NetworkTypeIconModel> = buildState {
+ combineTransactionally(defaultNetworkType, carrierIdIconOverrideExists) {
+ networkType,
+ overrideExists ->
// DefaultIcon comes out of the icongroup lookup, we check for overrides here
if (overrideExists) {
val iconOverride =
carrierIdOverrides.getOverrideFor(
- connectionRepository.carrierId.value,
+ connectionRepository.carrierId.sample(),
networkType.name,
context.resources,
)
@@ -233,106 +223,101 @@ class MobileIconInteractorKairosImpl(
DefaultIcon(networkType)
}
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- initialValue = DefaultIcon(defaultMobileIconGroup.value),
- )
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- DefaultIcon(defaultMobileIconGroup.value),
- )
+ .also { logDiffsForTable(it, tableLogBuffer = tableLogBuffer) }
+ }
- override val showSliceAttribution: StateFlow<Boolean> =
+ override val showSliceAttribution: State<Boolean> =
combine(
- connectionRepository.allowNetworkSliceIndicator,
- connectionRepository.hasPrioritizedNetworkCapabilities,
- ) { allowed, hasPrioritizedNetworkCapabilities ->
- allowed && hasPrioritizedNetworkCapabilities
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ connectionRepository.allowNetworkSliceIndicator,
+ connectionRepository.hasPrioritizedNetworkCapabilities,
+ ) { allowed, hasPrioritizedNetworkCapabilities ->
+ allowed && hasPrioritizedNetworkCapabilities
+ }
- override val isNonTerrestrial: StateFlow<Boolean> = connectionRepository.isNonTerrestrial
+ override val isNonTerrestrial: State<Boolean>
+ get() = connectionRepository.isNonTerrestrial
- override val isRoaming: StateFlow<Boolean> =
+ override val isRoaming: State<Boolean> =
combine(
- connectionRepository.carrierNetworkChangeActive,
- connectionRepository.isGsm,
- connectionRepository.isRoaming,
- connectionRepository.cdmaRoaming,
- ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
- if (carrierNetworkChangeActive) {
- false
- } else if (isGsm) {
- isRoaming
- } else {
- cdmaRoaming
- }
+ connectionRepository.carrierNetworkChangeActive,
+ connectionRepository.isGsm,
+ connectionRepository.isRoaming,
+ connectionRepository.cdmaRoaming,
+ ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
+ if (carrierNetworkChangeActive) {
+ false
+ } else if (isGsm) {
+ isRoaming
+ } else {
+ cdmaRoaming
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ }
- private val level: StateFlow<Int> =
+ private val level: State<Int> =
combine(
- connectionRepository.isGsm,
- connectionRepository.primaryLevel,
- connectionRepository.cdmaLevel,
- alwaysUseCdmaLevel,
- ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
- when {
- // GSM connections should never use the CDMA level
- isGsm -> primaryLevel
- alwaysUseCdmaLevel -> cdmaLevel
- else -> primaryLevel
- }
+ connectionRepository.isGsm,
+ connectionRepository.primaryLevel,
+ connectionRepository.cdmaLevel,
+ alwaysUseCdmaLevel,
+ ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
+ when {
+ // GSM connections should never use the CDMA level
+ isGsm -> primaryLevel
+ alwaysUseCdmaLevel -> cdmaLevel
+ else -> primaryLevel
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+ }
- private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels
+ private val numberOfLevels: State<Int>
+ get() = connectionRepository.numberOfLevels
- override val isDataConnected: StateFlow<Boolean> =
+ override val isDataConnected: State<Boolean> =
connectionRepository.dataConnectionState
.map { it == Connected }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, "icon", "isDataConnected") }
+ }
- override val isInService = connectionRepository.isInService
+ override val isInService
+ get() = connectionRepository.isInService
- override val isEmergencyOnly: StateFlow<Boolean> = connectionRepository.isEmergencyOnly
+ override val isEmergencyOnly: State<Boolean>
+ get() = connectionRepository.isEmergencyOnly
- override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
+ override val isAllowedDuringAirplaneMode: State<Boolean>
+ get() = connectionRepository.isAllowedDuringAirplaneMode
/** Whether or not to show the error state of [SignalDrawable] */
- private val showExclamationMark: StateFlow<Boolean> =
+ private val showExclamationMark: State<Boolean> =
combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
- isDefaultDataEnabled,
- isDefaultConnectionFailed,
- isInService ->
- !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+ isDefaultDataEnabled,
+ isDefaultConnectionFailed,
+ isInService ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
+ }
- private val cellularShownLevel: StateFlow<Int> =
+ private val cellularShownLevel: State<Int> =
combine(level, isInService, connectionRepository.inflateSignalStrength) {
- level,
- isInService,
- inflate ->
- if (isInService) {
- if (inflate) level + 1 else level
- } else 0
+ level,
+ isInService,
+ inflate ->
+ when {
+ !isInService -> 0
+ inflate -> level + 1
+ else -> level
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+ }
// Satellite level is unaffected by the inflateSignalStrength property
// See b/346904529 for details
- private val satelliteShownLevel: StateFlow<Int> =
+ private val satelliteShownLevel: State<Int> =
if (Flags.carrierRoamingNbIotNtn()) {
- connectionRepository.satelliteLevel
- } else {
- combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+ connectionRepository.satelliteLevel
+ } else {
+ combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ }
- private val cellularIcon: Flow<SignalIconModel.Cellular> =
+ private val cellularIcon: State<SignalIconModel.Cellular> =
combine(
cellularShownLevel,
numberOfLevels,
@@ -347,7 +332,7 @@ class MobileIconInteractorKairosImpl(
)
}
- private val satelliteIcon: Flow<SignalIconModel.Satellite> =
+ private val satelliteIcon: State<SignalIconModel.Satellite> =
satelliteShownLevel.map {
SignalIconModel.Satellite(
level = it,
@@ -357,24 +342,14 @@ class MobileIconInteractorKairosImpl(
)
}
- override val signalLevelIcon: StateFlow<SignalIconModel> = run {
- val initial =
- SignalIconModel.Cellular(
- cellularShownLevel.value,
- numberOfLevels.value,
- showExclamationMark.value,
- carrierNetworkChangeActive.value,
- )
+ override val signalLevelIcon: State<SignalIconModel> =
isNonTerrestrial
- .flatMapLatest { ntn ->
+ .flatMap { ntn ->
if (ntn) {
satelliteIcon
} else {
cellularIcon
}
}
- .distinctUntilChanged()
- .logDiffsForTable(tableLogBuffer, columnPrefix = "icon", initialValue = initial)
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "icon") } }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt
new file mode 100644
index 000000000000..a3b9b17257cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.domain.interactor
+
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.toColdConflatedFlow
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+@ExperimentalKairosApi
+fun BuildScope.MobileIconInteractorKairosAdapter(
+ kairosImpl: MobileIconInteractorKairos
+): MobileIconInteractor =
+ with(kairosImpl) {
+ MobileIconInteractorKairosAdapter(
+ tableLogBuffer = tableLogBuffer,
+ activity = activity.toColdConflatedFlow(kairosNetwork),
+ mobileIsDefault = mobileIsDefault.toColdConflatedFlow(kairosNetwork),
+ isDataConnected = isDataConnected.toStateFlow(),
+ isInService = isInService.toStateFlow(),
+ isEmergencyOnly = isEmergencyOnly.toStateFlow(),
+ isDataEnabled = isDataEnabled.toStateFlow(),
+ alwaysShowDataRatIcon = alwaysShowDataRatIcon.toStateFlow(),
+ signalLevelIcon = signalLevelIcon.toStateFlow(),
+ networkTypeIconGroup = networkTypeIconGroup.toStateFlow(),
+ showSliceAttribution = showSliceAttribution.toStateFlow(),
+ isNonTerrestrial = isNonTerrestrial.toStateFlow(),
+ networkName = networkName.toStateFlow(),
+ carrierName = carrierName.toStateFlow(),
+ isSingleCarrier = isSingleCarrier.toStateFlow(),
+ isRoaming = isRoaming.toStateFlow(),
+ isForceHidden = isForceHidden.toColdConflatedFlow(kairosNetwork),
+ isAllowedDuringAirplaneMode = isAllowedDuringAirplaneMode.toStateFlow(),
+ carrierNetworkChangeActive = carrierNetworkChangeActive.toStateFlow(),
+ )
+ }
+
+private class MobileIconInteractorKairosAdapter(
+ override val tableLogBuffer: TableLogBuffer,
+ override val activity: Flow<DataActivityModel>,
+ override val mobileIsDefault: Flow<Boolean>,
+ override val isDataConnected: StateFlow<Boolean>,
+ override val isInService: StateFlow<Boolean>,
+ override val isEmergencyOnly: StateFlow<Boolean>,
+ override val isDataEnabled: StateFlow<Boolean>,
+ override val alwaysShowDataRatIcon: StateFlow<Boolean>,
+ override val signalLevelIcon: StateFlow<SignalIconModel>,
+ override val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>,
+ override val showSliceAttribution: StateFlow<Boolean>,
+ override val isNonTerrestrial: StateFlow<Boolean>,
+ override val networkName: StateFlow<NetworkNameModel>,
+ override val carrierName: StateFlow<String>,
+ override val isSingleCarrier: StateFlow<Boolean>,
+ override val isRoaming: StateFlow<Boolean>,
+ override val isForceHidden: Flow<Boolean>,
+ override val isAllowedDuringAirplaneMode: StateFlow<Boolean>,
+ override val carrierNetworkChangeActive: StateFlow<Boolean>,
+) : MobileIconInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt
index e8e0a833af2a..14a276b60933 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,41 +21,47 @@ import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.asyncEvent
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.filter
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.flatten
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.util.CarrierConfigTracker
-import java.lang.ref.WeakReference
+import dagger.Binds
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Provider
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.transformLatest
/**
* Business layer logic for the set of mobile subscription icons.
@@ -67,98 +73,79 @@ import kotlinx.coroutines.flow.transformLatest
* represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual
* icon
*/
+@ExperimentalKairosApi
interface MobileIconsInteractorKairos {
/** See [MobileConnectionsRepository.mobileIsDefault]. */
- val mobileIsDefault: StateFlow<Boolean>
+ val mobileIsDefault: State<Boolean>
/** List of subscriptions, potentially filtered for CBRS */
- val filteredSubscriptions: Flow<List<SubscriptionModel>>
-
- /** Subscription ID of the current default data subscription */
- val defaultDataSubId: Flow<Int?>
+ val filteredSubscriptions: State<List<SubscriptionModel>>
/**
* The current list of [MobileIconInteractor]s associated with the current list of
* [filteredSubscriptions]
*/
- val icons: StateFlow<List<MobileIconInteractor>>
+ val icons: Incremental<Int, MobileIconInteractorKairos>
/** Whether the mobile icons can be stacked vertically. */
- val isStackable: StateFlow<Boolean>
-
- /**
- * Observable for the subscriptionId of the current mobile data connection. Null if we don't
- * have a valid subscription id
- */
- val activeMobileDataSubscriptionId: StateFlow<Int?>
+ val isStackable: State<Boolean>
/** True if the active mobile data subscription has data enabled */
- val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+ val activeDataConnectionHasDataEnabled: State<Boolean>
/**
* Flow providing a reference to the Interactor for the active data subId. This represents the
- * [MobileIconInteractor] responsible for the active data connection, if any.
+ * [MobileIconInteractorKairos] responsible for the active data connection, if any.
*/
- val activeDataIconInteractor: StateFlow<MobileIconInteractor?>
+ val activeDataIconInteractor: State<MobileIconInteractorKairos?>
/** True if the RAT icon should always be displayed and false otherwise. */
- val alwaysShowDataRatIcon: StateFlow<Boolean>
+ val alwaysShowDataRatIcon: State<Boolean>
/** True if the CDMA level should be preferred over the primary level. */
- val alwaysUseCdmaLevel: StateFlow<Boolean>
+ val alwaysUseCdmaLevel: State<Boolean>
/** True if there is only one active subscription. */
- val isSingleCarrier: StateFlow<Boolean>
+ val isSingleCarrier: State<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
- val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
+ val defaultMobileIconMapping: State<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
- val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+ val defaultMobileIconGroup: State<MobileIconGroup>
/** True only if the default network is mobile, and validation also failed */
- val isDefaultConnectionFailed: StateFlow<Boolean>
+ val isDefaultConnectionFailed: State<Boolean>
/** True once the user has been set up */
- val isUserSetUp: StateFlow<Boolean>
+ val isUserSetUp: State<Boolean>
/** True if we're configured to force-hide the mobile icons and false otherwise. */
- val isForceHidden: Flow<Boolean>
+ val isForceHidden: State<Boolean>
/**
* True if the device-level service state (with -1 subscription id) reports emergency calls
* only. This value is only useful when there are no other subscriptions OR all existing
* subscriptions report that they are not in service.
*/
- val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean>
-
- /**
- * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
- * subId.
- */
- fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+ val isDeviceInEmergencyCallsOnlyMode: State<Boolean>
}
-@OptIn(ExperimentalCoroutinesApi::class)
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@ExperimentalKairosApi
@SysUISingleton
class MobileIconsInteractorKairosImpl
@Inject
constructor(
- private val mobileConnectionsRepo: MobileConnectionsRepository,
+ private val mobileConnectionsRepo: MobileConnectionsRepositoryKairos,
private val carrierConfigTracker: CarrierConfigTracker,
@MobileSummaryLog private val tableLogger: TableLogBuffer,
connectivityRepository: ConnectivityRepository,
userSetupRepo: UserSetupRepository,
- @Background private val scope: CoroutineScope,
private val context: Context,
private val featureFlagsClassic: FeatureFlagsClassic,
-) : MobileIconsInteractor, MobileIconsInteractorKairos {
-
- // Weak reference lookup for created interactors
- private val reuseCache = mutableMapOf<Int, WeakReference<MobileIconInteractor>>()
+) : MobileIconsInteractorKairos, KairosBuilder by kairosBuilder() {
- override val mobileIsDefault =
+ override val mobileIsDefault: State<Boolean> =
combine(
mobileConnectionsRepo.mobileIsDefault,
mobileConnectionsRepo.hasCarrierMergedConnection,
@@ -167,47 +154,36 @@ constructor(
// the `isDefault` calculation. See b/272586234.
mobileIsDefault || hasCarrierMergedConnection
}
- .logDiffsForTable(
- tableLogger,
- LOGGING_PREFIX,
- columnName = "mobileIsDefault",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- override val activeMobileDataSubscriptionId: StateFlow<Int?> =
- mobileConnectionsRepo.activeMobileDataSubscriptionId
-
- override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
- mobileConnectionsRepo.activeMobileDataRepository
- .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> =
- mobileConnectionsRepo.activeMobileDataSubscriptionId
- .mapLatest {
- if (it != null) {
- getMobileConnectionInteractorForSubId(it)
- } else {
- null
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ )
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
- private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
- mobileConnectionsRepo.subscriptions
+ override val activeDataConnectionHasDataEnabled: State<Boolean> =
+ mobileConnectionsRepo.activeMobileDataRepository.flatMap {
+ it?.dataEnabled ?: stateOf(false)
+ }
+
+ private val unfilteredSubscriptions: State<Collection<SubscriptionModel>>
+ get() = mobileConnectionsRepo.subscriptions
/** Any filtering that we can do based purely on the info of each subscription individually. */
- private val subscriptionsBasedFilteredSubs =
- unfilteredSubscriptions
- .map { it.filterBasedOnProvisioning().filterBasedOnNtn() }
- .distinctUntilChanged()
+ private val subscriptionsBasedFilteredSubs: State<List<SubscriptionModel>> =
+ unfilteredSubscriptions.map {
+ it.asSequence().filterBasedOnProvisioning().filterBasedOnNtn().toList()
+ }
- private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> =
+ private fun Sequence<SubscriptionModel>.filterBasedOnProvisioning() =
if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
this
} else {
- this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+ filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
}
/**
@@ -219,9 +195,10 @@ constructor(
* need to filter out those subscriptions here so we guarantee the subscription never turns into
* an icon. See b/336881301.
*/
- private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> {
- return this.filter { !it.isExclusivelyNonTerrestrial }
- }
+ private fun Sequence<SubscriptionModel>.filterBasedOnNtn(): Sequence<SubscriptionModel> =
+ filter {
+ !it.isExclusivelyNonTerrestrial
+ }
/**
* Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -236,22 +213,23 @@ constructor(
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
* and by checking which subscription is opportunistic, or which one is active.
*/
- override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
+ override val filteredSubscriptions: State<List<SubscriptionModel>> = buildState {
combine(
subscriptionsBasedFilteredSubs,
mobileConnectionsRepo.activeMobileDataSubscriptionId,
- connectivityRepository.vcnSubId,
+ connectivityRepository.vcnSubId.toState(),
) { preFilteredSubs, activeId, vcnSubId ->
filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLogger,
- LOGGING_PREFIX,
- columnName = "filteredSubscriptions",
- initialValue = listOf(),
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ .also {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "filteredSubscriptions",
+ )
+ }
+ }
private fun filterSubsBasedOnOpportunistic(
subList: List<SubscriptionModel>,
@@ -298,19 +276,25 @@ constructor(
}
}
- override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
-
- override val icons =
- filteredSubscriptions
- .mapLatest { subs ->
- subs.map { getMobileConnectionInteractorForSubId(it.subscriptionId) }
+ override val icons: Incremental<Int, MobileIconInteractorKairos> = buildIncremental {
+ val filteredSubIds =
+ filteredSubscriptions.map { it.asSequence().map { sub -> sub.subscriptionId }.toSet() }
+ mobileConnectionsRepo.mobileConnectionsBySubId
+ .filterIncrementally { (subId, _) ->
+ // Filter out repo if subId is not present in the filtered set
+ filteredSubIds.map { subId in it }
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+ // Just map the repos to interactors
+ .mapValues { (subId, repo) -> buildSpec { mobileConnection(repo) } }
+ .applyLatestSpecForKey()
+ }
- override val isStackable =
+ override val isStackable: State<Boolean> =
if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) {
- icons.flatMapLatest { icons ->
- combine(icons.map { it.signalLevelIcon }) { signalLevelIcons ->
+ icons.flatMap { iconsBySubId: Map<Int, MobileIconInteractorKairos> ->
+ iconsBySubId.values
+ .map { it.signalLevelIcon }
+ .combine { signalLevelIcons ->
// These are only stackable if:
// - They are cellular
// - There's exactly two
@@ -319,11 +303,15 @@ constructor(
it.size == 2 && it[0].numberOfLevels == it[1].numberOfLevels
}
}
- }
- } else {
- flowOf(false)
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ } else {
+ stateOf(false)
+ }
+
+ override val activeDataIconInteractor: State<MobileIconInteractorKairos?> =
+ combine(mobileConnectionsRepo.activeMobileDataSubscriptionId, icons) { activeSubId, icons ->
+ activeSubId?.let { icons[activeSubId] }
+ }
/**
* Copied from the old pipeline. We maintain a 2s period of time where we will keep the
@@ -335,67 +323,59 @@ constructor(
*
* The goal of this is to minimize the flickering in the UI of the cellular indicator
*/
- private val forcingCellularValidation =
+ private val forcingCellularValidation: State<Boolean> = buildState {
mobileConnectionsRepo.activeSubChangedInGroupEvent
- .filter { mobileConnectionsRepo.defaultConnectionIsValidated.value }
- .transformLatest {
- emit(true)
- delay(2000)
- emit(false)
+ .filter(mobileConnectionsRepo.defaultConnectionIsValidated)
+ .mapLatestBuild {
+ asyncEvent {
+ delay(2.seconds)
+ false
+ }
+ .holdState(true)
+ }
+ .holdState(stateOf(false))
+ .flatten()
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "forcingValidation")
}
- .logDiffsForTable(
- tableLogger,
- LOGGING_PREFIX,
- columnName = "forcingValidation",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ }
/**
* Mapping from network type to [MobileIconGroup] using the config generated for the default
* subscription Id. This mapping is the same for every subscription.
*/
- override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
- mobileConnectionsRepo.defaultMobileIconMapping.stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- initialValue = mapOf(),
- )
+ override val defaultMobileIconMapping: State<Map<String, MobileIconGroup>>
+ get() = mobileConnectionsRepo.defaultMobileIconMapping
- override val alwaysShowDataRatIcon: StateFlow<Boolean> =
- mobileConnectionsRepo.defaultDataSubRatConfig
- .mapLatest { it.alwaysShowDataRatIcon }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val alwaysShowDataRatIcon: State<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig.map { it.alwaysShowDataRatIcon }
- override val alwaysUseCdmaLevel: StateFlow<Boolean> =
- mobileConnectionsRepo.defaultDataSubRatConfig
- .mapLatest { it.alwaysShowCdmaRssi }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val alwaysUseCdmaLevel: State<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig.map { it.alwaysShowCdmaRssi }
- override val isSingleCarrier: StateFlow<Boolean> =
+ override val isSingleCarrier: State<Boolean> =
mobileConnectionsRepo.subscriptions
.map { it.size == 1 }
- .logDiffsForTable(
- tableLogger,
- columnPrefix = LOGGING_PREFIX,
- columnName = "isSingleCarrier",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "isSingleCarrier",
+ )
+ }
+ }
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
- override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
- mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- initialValue = TelephonyIcons.G,
- )
+ override val defaultMobileIconGroup: State<MobileIconGroup>
+ get() = mobileConnectionsRepo.defaultMobileIconGroup
/**
* We want to show an error state when cellular has actually failed to validate, but not if some
* other transport type is active, because then we expect there not to be validation.
*/
- override val isDefaultConnectionFailed: StateFlow<Boolean> =
+ override val isDefaultConnectionFailed: State<Boolean> =
combine(
mobileIsDefault,
mobileConnectionsRepo.defaultConnectionIsValidated,
@@ -407,46 +387,63 @@ constructor(
else -> !defaultConnectionIsValidated
}
}
- .logDiffsForTable(
- tableLogger,
- LOGGING_PREFIX,
- columnName = "isDefaultConnectionFailed",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- override val isUserSetUp: StateFlow<Boolean> = userSetupRepo.isUserSetUp
-
- override val isForceHidden: Flow<Boolean> =
- connectivityRepository.forceHiddenSlots
- .map { it.contains(ConnectivitySlot.MOBILE) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
- mobileConnectionsRepo.isDeviceEmergencyCallCapable
-
- /** Vends out new [MobileIconInteractor] for a particular subId */
- override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
- reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId)
-
- private fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
- MobileIconInteractorImpl(
- scope,
- activeDataConnectionHasDataEnabled,
- alwaysShowDataRatIcon,
- alwaysUseCdmaLevel,
- isSingleCarrier,
- mobileIsDefault,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- isDefaultConnectionFailed,
- isForceHidden,
- mobileConnectionsRepo.getRepoForSubId(subId),
- context,
- )
- .also { reuseCache[subId] = WeakReference(it) }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "isDefaultConnectionFailed",
+ )
+ }
+ }
+
+ override val isUserSetUp: State<Boolean> = buildState { userSetupRepo.isUserSetUp.toState() }
+
+ override val isForceHidden: State<Boolean> = buildState {
+ connectivityRepository.forceHiddenSlots.toState().map {
+ it.contains(ConnectivitySlot.MOBILE)
+ }
+ }
+
+ override val isDeviceInEmergencyCallsOnlyMode: State<Boolean>
+ get() = mobileConnectionsRepo.isDeviceEmergencyCallCapable
+
+ /** Vends out a new [MobileIconInteractorKairos] for a particular subId */
+ private fun BuildScope.mobileConnection(
+ repo: MobileConnectionRepositoryKairos
+ ): MobileIconInteractorKairos = activated {
+ MobileIconInteractorKairosImpl(
+ activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel,
+ isSingleCarrier,
+ mobileIsDefault,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ isDefaultConnectionFailed,
+ isForceHidden,
+ repo,
+ context,
+ )
+ }
companion object {
private const val LOGGING_PREFIX = "Intr"
}
+
+ @dagger.Module
+ interface Module {
+
+ @Binds fun bindImpl(impl: MobileIconsInteractorKairosImpl): MobileIconsInteractorKairos
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileIconsInteractorKairosImpl>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
new file mode 100644
index 000000000000..87877b3e9f43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.domain.interactor
+
+import android.content.Context
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.toColdConflatedFlow
+import com.android.systemui.kairosBuilder
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileIconsInteractorKairosAdapter
+@Inject
+constructor(
+ private val kairosInteractor: MobileIconsInteractorKairos,
+ private val repo: MobileConnectionsRepository,
+ repoK: MobileConnectionsRepositoryKairos,
+ kairosNetwork: KairosNetwork,
+ @Application scope: CoroutineScope,
+ context: Context,
+ mobileMappingsProxy: MobileMappingsProxy,
+ private val userSetupRepo: UserSetupRepository,
+) : MobileIconsInteractor, KairosBuilder by kairosBuilder() {
+
+ private val interactorsBySubIdK = buildIncremental {
+ kairosInteractor.icons
+ .mapValues { (subId, interactor) ->
+ buildSpec { MobileIconInteractorKairosAdapter(interactor) }
+ }
+ .applyLatestSpecForKey()
+ }
+
+ private val interactorsBySubId =
+ interactorsBySubIdK
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, emptyMap())
+
+ override val defaultDataSubId: Flow<Int?>
+ get() = repo.defaultDataSubId
+
+ override val mobileIsDefault: StateFlow<Boolean> =
+ kairosInteractor.mobileIsDefault
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), repo.mobileIsDefault.value)
+
+ override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
+ kairosInteractor.filteredSubscriptions.toColdConflatedFlow(kairosNetwork)
+
+ override val icons: StateFlow<List<MobileIconInteractor>> =
+ interactorsBySubIdK
+ .map { it.values.toList() }
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override val isStackable: StateFlow<Boolean> =
+ kairosInteractor.isStackable
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activeMobileDataSubscriptionId: StateFlow<Int?>
+ get() = repo.activeMobileDataSubscriptionId
+
+ override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+ kairosInteractor.activeDataConnectionHasDataEnabled
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> =
+ combine(repoK.activeMobileDataSubscriptionId, interactorsBySubIdK) { subId, interactors ->
+ interactors[subId]
+ }
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val alwaysShowDataRatIcon: StateFlow<Boolean> =
+ kairosInteractor.alwaysShowDataRatIcon
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val alwaysUseCdmaLevel: StateFlow<Boolean> =
+ kairosInteractor.alwaysUseCdmaLevel
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isSingleCarrier: StateFlow<Boolean> =
+ kairosInteractor.isSingleCarrier
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val defaultMobileIconMapping: StateFlow<Map<String, SignalIcon.MobileIconGroup>> =
+ kairosInteractor.defaultMobileIconMapping
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyMap())
+
+ override val defaultMobileIconGroup: StateFlow<SignalIcon.MobileIconGroup> =
+ kairosInteractor.defaultMobileIconGroup
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ mobileMappingsProxy.getDefaultIcons(MobileMappings.Config.readConfig(context)),
+ )
+
+ override val isDefaultConnectionFailed: StateFlow<Boolean> =
+ kairosInteractor.isDefaultConnectionFailed
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isUserSetUp: StateFlow<Boolean>
+ get() = userSetupRepo.isUserSetUp
+
+ override val isForceHidden: Flow<Boolean> =
+ kairosInteractor.isForceHidden
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean>
+ get() = repo.isDeviceEmergencyCallCapable
+
+ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId")
+
+ @dagger.Module
+ object Module {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileIconsInteractorKairosAdapter>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt
new file mode 100644
index 000000000000..fce8c85338f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
+ * allows the mobile icon to change some view parameters at different locations
+ *
+ * @param commonImpl for convenience, this class wraps a base interface that can provides all of the
+ * common implementations between locations. See [MobileIconViewModel]
+ * @property location the [StatusBarLocation] of this VM.
+ * @property verboseLogger an optional logger to log extremely verbose view updates.
+ */
+abstract class LocationBasedMobileViewModelKairos(
+ val commonImpl: MobileIconViewModelCommonKairos,
+ val location: StatusBarLocation,
+ val verboseLogger: VerboseMobileViewLogger?,
+) : MobileIconViewModelCommonKairos by commonImpl {
+ val defaultColor: Int = Color.WHITE
+
+ companion object {
+ fun viewModelForLocation(
+ commonImpl: MobileIconViewModelCommon,
+ interactor: MobileIconInteractor,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
+ location: StatusBarLocation,
+ scope: CoroutineScope,
+ ): LocationBasedMobileViewModel =
+ when (location) {
+ StatusBarLocation.HOME ->
+ HomeMobileIconViewModel(commonImpl, verboseMobileViewLogger)
+ StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl)
+ StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl)
+ StatusBarLocation.SHADE_CARRIER_GROUP ->
+ ShadeCarrierGroupMobileIconViewModel(commonImpl, interactor, scope)
+ }
+ }
+}
+
+class HomeMobileIconViewModelKairos(
+ commonImpl: MobileIconViewModelCommonKairos,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
+) :
+ MobileIconViewModelCommonKairos,
+ LocationBasedMobileViewModelKairos(
+ commonImpl,
+ location = StatusBarLocation.HOME,
+ verboseMobileViewLogger,
+ )
+
+class QsMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) :
+ MobileIconViewModelCommonKairos,
+ LocationBasedMobileViewModelKairos(
+ commonImpl,
+ location = StatusBarLocation.QS,
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
+
+class ShadeCarrierGroupMobileIconViewModelKairos(
+ commonImpl: MobileIconViewModelCommonKairos,
+ interactor: MobileIconInteractor,
+ scope: CoroutineScope,
+) :
+ MobileIconViewModelCommonKairos,
+ LocationBasedMobileViewModelKairos(
+ commonImpl,
+ location = StatusBarLocation.SHADE_CARRIER_GROUP,
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ ) {
+ private val isSingleCarrier = interactor.isSingleCarrier
+ val carrierName = interactor.carrierName
+
+ override val isVisible: StateFlow<Boolean> =
+ combine(super.isVisible, isSingleCarrier) { isVisible, isSingleCarrier ->
+ if (isSingleCarrier) false else isVisible
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), super.isVisible.value)
+}
+
+class KeyguardMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) :
+ MobileIconViewModelCommonKairos,
+ LocationBasedMobileViewModelKairos(
+ commonImpl,
+ location = StatusBarLocation.KEYGUARD,
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt
new file mode 100644
index 000000000000..cc7fc0964dae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/** Common interface for all of the location-based mobile icon view models. */
+interface MobileIconViewModelCommonKairos : MobileIconViewModelCommon {
+ override val subscriptionId: Int
+ /** True if this view should be visible at all. */
+ override val isVisible: StateFlow<Boolean>
+ override val icon: Flow<SignalIconModel>
+ override val contentDescription: Flow<MobileContentDescription?>
+ override val roaming: Flow<Boolean>
+ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+ override val networkTypeIcon: Flow<Icon.Resource?>
+ /** The slice attribution. Drawn as a background layer */
+ override val networkTypeBackground: StateFlow<Icon.Resource?>
+ override val activityInVisible: Flow<Boolean>
+ override val activityOutVisible: Flow<Boolean>
+ override val activityContainerVisible: Flow<Boolean>
+}
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions].
+ *
+ * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
+ * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
+ * model gets the exact same information, as well as allows us to log that unified state only once
+ * per icon.
+ */
+class MobileIconViewModelKairos(
+ override val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ airplaneModeInteractor: AirplaneModeInteractor,
+ constants: ConnectivityConstants,
+ scope: CoroutineScope,
+) : MobileIconViewModelCommonKairos {
+ private val cellProvider by lazy {
+ CellularIconViewModelKairos(
+ subscriptionId,
+ iconInteractor,
+ airplaneModeInteractor,
+ constants,
+ scope,
+ )
+ }
+
+ private val satelliteProvider by lazy {
+ CarrierBasedSatelliteViewModelKairosImpl(
+ subscriptionId,
+ airplaneModeInteractor,
+ iconInteractor,
+ scope,
+ )
+ }
+
+ /**
+ * Similar to repository switching, this allows us to split up the logic of satellite/cellular
+ * states, since they are different by nature
+ */
+ private val vmProvider: Flow<MobileIconViewModelCommon> =
+ iconInteractor.isNonTerrestrial
+ .mapLatest { nonTerrestrial ->
+ if (nonTerrestrial) {
+ satelliteProvider
+ } else {
+ cellProvider
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)
+
+ override val isVisible: StateFlow<Boolean> =
+ vmProvider
+ .flatMapLatest { it.isVisible }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
+
+ override val contentDescription: Flow<MobileContentDescription?> =
+ vmProvider.flatMapLatest { it.contentDescription }
+
+ override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
+
+ override val networkTypeIcon: Flow<Icon.Resource?> =
+ vmProvider.flatMapLatest { it.networkTypeIcon }
+
+ override val networkTypeBackground: StateFlow<Icon.Resource?> =
+ vmProvider
+ .flatMapLatest { it.networkTypeBackground }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activityInVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityInVisible }
+
+ override val activityOutVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityOutVisible }
+
+ override val activityContainerVisible: Flow<Boolean> =
+ vmProvider.flatMapLatest { it.activityContainerVisible }
+}
+
+/** Representation of this network when it is non-terrestrial (e.g., satellite) */
+private class CarrierBasedSatelliteViewModelKairosImpl(
+ override val subscriptionId: Int,
+ airplaneModeInteractor: AirplaneModeInteractor,
+ interactor: MobileIconInteractor,
+ scope: CoroutineScope,
+) : MobileIconViewModelCommon, MobileIconViewModelCommonKairos {
+ override val isVisible: StateFlow<Boolean> =
+ airplaneModeInteractor.isAirplaneMode
+ .map { !it }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon
+
+ override val contentDescription: Flow<MobileContentDescription?> = MutableStateFlow(null)
+
+ /** These fields are not used for satellite icons currently */
+ override val roaming: Flow<Boolean> = flowOf(false)
+ override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
+ override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
+ override val activityInVisible: Flow<Boolean> = flowOf(false)
+ override val activityOutVisible: Flow<Boolean> = flowOf(false)
+ override val activityContainerVisible: Flow<Boolean> = flowOf(false)
+}
+
+/** Terrestrial (cellular) icon. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+private class CellularIconViewModelKairos(
+ override val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ airplaneModeInteractor: AirplaneModeInteractor,
+ constants: ConnectivityConstants,
+ scope: CoroutineScope,
+) : MobileIconViewModelCommon, MobileIconViewModelCommonKairos {
+ override val isVisible: StateFlow<Boolean> =
+ if (!constants.hasDataCapabilities) {
+ flowOf(false)
+ } else {
+ combine(
+ airplaneModeInteractor.isAirplaneMode,
+ iconInteractor.isAllowedDuringAirplaneMode,
+ iconInteractor.isForceHidden,
+ ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
+ if (isForceHidden) {
+ false
+ } else if (isAirplaneMode) {
+ isAllowedDuringAirplaneMode
+ } else {
+ true
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnName = "visible",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon
+
+ override val contentDescription: Flow<MobileContentDescription?> =
+ combine(iconInteractor.signalLevelIcon, iconInteractor.networkName) { icon, nameModel ->
+ when (icon) {
+ is SignalIconModel.Cellular ->
+ MobileContentDescription.Cellular(
+ nameModel.name,
+ icon.levelDescriptionRes(),
+ )
+ else -> null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private fun SignalIconModel.Cellular.levelDescriptionRes() =
+ when (level) {
+ 0 -> R.string.accessibility_no_signal
+ 1 -> R.string.accessibility_one_bar
+ 2 -> R.string.accessibility_two_bars
+ 3 -> R.string.accessibility_three_bars
+ 4 -> {
+ if (numberOfLevels == 6) {
+ R.string.accessibility_four_bars
+ } else {
+ R.string.accessibility_signal_full
+ }
+ }
+ 5 -> {
+ if (numberOfLevels == 6) {
+ R.string.accessibility_signal_full
+ } else {
+ R.string.accessibility_no_signal
+ }
+ }
+ else -> R.string.accessibility_no_signal
+ }
+
+ private val showNetworkTypeIcon: Flow<Boolean> =
+ combine(
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.alwaysShowDataRatIcon,
+ iconInteractor.mobileIsDefault,
+ iconInteractor.carrierNetworkChangeActive,
+ ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange ->
+ alwaysShow ||
+ (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault))
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnName = "showNetworkTypeIcon",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val networkTypeIcon: Flow<Icon.Resource?> =
+ combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) {
+ networkTypeIconGroup,
+ shouldShow ->
+ val desc =
+ if (networkTypeIconGroup.contentDescription != 0)
+ ContentDescription.Resource(networkTypeIconGroup.contentDescription)
+ else null
+ val icon =
+ if (networkTypeIconGroup.iconId != 0)
+ Icon.Resource(networkTypeIconGroup.iconId, desc)
+ else null
+ return@combine when {
+ !shouldShow -> null
+ else -> icon
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val networkTypeBackground =
+ iconInteractor.showSliceAttribution
+ .map {
+ when {
+ it && NewStatusBarIcons.isEnabled ->
+ Icon.Resource(R.drawable.mobile_network_type_background_updated, null)
+ it -> Icon.Resource(R.drawable.mobile_network_type_background, null)
+ else -> null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val roaming: StateFlow<Boolean> =
+ iconInteractor.isRoaming
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnName = "roaming",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private val activity: Flow<DataActivityModel?> =
+ if (!constants.shouldShowActivityConfig) {
+ flowOf(null)
+ } else {
+ iconInteractor.activity
+ }
+
+ override val activityInVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityIn ?: false }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activityOutVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityOut ?: false }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activityContainerVisible: Flow<Boolean> =
+ if (statusBarStaticInoutIndicators()) {
+ flowOf(constants.shouldShowActivityConfig)
+ } else {
+ activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt
new file mode 100644
index 000000000000..a65540738828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.coroutines.newTracingContext
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView].
+ */
+@SysUISingleton
+class MobileIconsViewModelKairos
+@Inject
+constructor(
+ val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
+ private val interactor: MobileIconsInteractor,
+ private val airplaneModeInteractor: AirplaneModeInteractor,
+ private val constants: ConnectivityConstants,
+ @Background private val scope: CoroutineScope,
+) {
+ @VisibleForTesting
+ val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>()
+
+ val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId
+
+ val subscriptionIdsFlow: StateFlow<List<Int>> =
+ interactor.filteredSubscriptions
+ .mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ val mobileSubViewModels: StateFlow<List<MobileIconViewModelCommon>> =
+ subscriptionIdsFlow
+ .map { ids -> ids.map { commonViewModelForSub(it) } }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> =
+ mobileSubViewModels
+ .map {
+ if (it.isEmpty()) {
+ null
+ } else {
+ // Mobile icons get reversed by [StatusBarIconController], so the last element
+ // in this list will show up visually first.
+ it.last()
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ /**
+ * A flow that emits `true` if the mobile sub that's displayed first visually is showing its
+ * network type icon and `false` otherwise.
+ */
+ val firstMobileSubShowingNetworkTypeIcon: StateFlow<Boolean> =
+ firstMobileSubViewModel
+ .flatMapLatest { firstMobileSubViewModel ->
+ firstMobileSubViewModel?.networkTypeIcon?.map { it != null } ?: flowOf(false)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ val isStackable: StateFlow<Boolean> = interactor.isStackable
+
+ init {
+ scope.launch { subscriptionIdsFlow.collect { invalidateCaches(it) } }
+ }
+
+ fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
+ val common = commonViewModelForSub(subId)
+ return LocationBasedMobileViewModel.viewModelForLocation(
+ common,
+ interactor.getMobileConnectionInteractorForSubId(subId),
+ verboseLogger,
+ location,
+ scope,
+ )
+ }
+
+ private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon {
+ return reuseCache.getOrPut(subId) { createViewModel(subId) }.first
+ }
+
+ private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
+ // Create a child scope so we can cancel it
+ val vmScope = scope.createChildScope(newTracingContext("MobileIconViewModel"))
+ val vm =
+ MobileIconViewModel(
+ subId,
+ interactor.getMobileConnectionInteractorForSubId(subId),
+ airplaneModeInteractor,
+ constants,
+ vmScope,
+ )
+
+ return Pair(vm, vmScope)
+ }
+
+ private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) =
+ CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext)
+
+ private fun invalidateCaches(subIds: List<Int>) {
+ reuseCache.keys
+ .filter { !subIds.contains(it) }
+ .forEach { id ->
+ reuseCache
+ .remove(id)
+ // Cancel the view model's scope after removing it
+ ?.second
+ ?.cancel()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt
new file mode 100644
index 000000000000..2dbb02c8f095
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StackedMobileIconViewModelKairos
+@AssistedInject
+constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() {
+ private val hydrator = Hydrator("StackedMobileIconViewModel")
+
+ private val isStackable: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isStackable",
+ source = mobileIconsViewModel.isStackable,
+ initialValue = false,
+ )
+
+ private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> =
+ combine(
+ mobileIconsViewModel.mobileSubViewModels,
+ mobileIconsViewModel.activeMobileDataSubscriptionId,
+ ) { viewModels, activeSubId ->
+ // Sort to get the active subscription first, if it's set
+ viewModels.sortedByDescending { it.subscriptionId == activeSubId }
+ }
+
+ val dualSim: DualSim? by
+ hydrator.hydratedStateOf(
+ traceName = "dualSim",
+ source =
+ iconViewModelFlow.flatMapLatest { viewModels ->
+ combine(viewModels.map { it.icon }) { icons ->
+ icons
+ .toList()
+ .filterIsInstance<SignalIconModel.Cellular>()
+ .takeIf { it.size == 2 }
+ ?.let { DualSim(it[0], it[1]) }
+ }
+ },
+ initialValue = null,
+ )
+
+ val networkTypeIcon: Icon.Resource? by
+ hydrator.hydratedStateOf(
+ traceName = "networkTypeIcon",
+ source =
+ iconViewModelFlow.flatMapLatest { viewModels ->
+ viewModels.firstOrNull()?.networkTypeIcon ?: flowOf(null)
+ },
+ initialValue = null,
+ )
+
+ val isIconVisible: Boolean by derivedStateOf { isStackable && dualSim != null }
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): StackedMobileIconViewModelKairos
+ }
+
+ data class DualSim(
+ val primary: SignalIconModel.Cellular,
+ val secondary: SignalIconModel.Cellular,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 982f6ec36150..2a6ff3b98ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -28,7 +28,7 @@ import android.telephony.satellite.SatelliteModemStateCallback
import android.telephony.satellite.SatelliteProvisionStateCallback
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index d7348892356d..6fada264e397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -47,7 +47,8 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import javax.inject.Inject
-import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
/**
@@ -153,39 +154,43 @@ constructor(
OngoingActivityChipBinder.createBinding(primaryChipView)
launch {
- viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
- OngoingActivityChipBinder.bind(
- primaryChipModel,
- primaryChipViewBinding,
- iconViewStore,
+ combine(
+ viewModel.primaryOngoingActivityChip,
+ viewModel.canShowOngoingActivityChips,
+ ::Pair,
)
+ .distinctUntilChanged()
+ .collect { (primaryChipModel, areChipsAllowed) ->
+ OngoingActivityChipBinder.bind(
+ primaryChipModel,
+ primaryChipViewBinding,
+ iconViewStore,
+ )
- if (StatusBarRootModernization.isEnabled) {
- launch {
+ if (StatusBarRootModernization.isEnabled) {
bindLegacyPrimaryOngoingActivityChipWithVisibility(
- viewModel,
+ areChipsAllowed,
primaryChipModel,
primaryChipViewBinding,
)
- }
- } else {
- when (primaryChipModel) {
- is OngoingActivityChipModel.Active ->
- listener?.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = true,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = true,
- )
-
- is OngoingActivityChipModel.Inactive ->
- listener?.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = false,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = primaryChipModel.shouldAnimate,
- )
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Active ->
+ listener?.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = true,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = true,
+ )
+
+ is OngoingActivityChipModel.Inactive ->
+ listener?.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = false,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = primaryChipModel.shouldAnimate,
+ )
+ }
}
}
- }
}
}
@@ -198,50 +203,56 @@ constructor(
OngoingActivityChipBinder.createBinding(
view.requireViewById(R.id.ongoing_activity_chip_secondary)
)
+ OngoingActivityChipBinder.updateTypefaces(primaryChipViewBinding)
+ OngoingActivityChipBinder.updateTypefaces(secondaryChipViewBinding)
launch {
- viewModel.ongoingActivityChipsLegacy.collectLatest { chips ->
- OngoingActivityChipBinder.bind(
- chips.primary,
- primaryChipViewBinding,
- iconViewStore,
+ combine(
+ viewModel.ongoingActivityChipsLegacy,
+ viewModel.canShowOngoingActivityChips,
+ ::Pair,
)
- OngoingActivityChipBinder.bind(
- chips.secondary,
- secondaryChipViewBinding,
- iconViewStore,
- )
-
- if (StatusBarRootModernization.isEnabled) {
- launch {
+ .distinctUntilChanged()
+ .collect { (chips, areChipsAllowed) ->
+ OngoingActivityChipBinder.bind(
+ chips.primary,
+ primaryChipViewBinding,
+ iconViewStore,
+ )
+ OngoingActivityChipBinder.bind(
+ chips.secondary,
+ secondaryChipViewBinding,
+ iconViewStore,
+ )
+ if (StatusBarRootModernization.isEnabled) {
bindOngoingActivityChipsWithVisibility(
- viewModel,
+ areChipsAllowed,
chips,
primaryChipViewBinding,
secondaryChipViewBinding,
)
+ } else {
+ listener?.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity =
+ chips.primary is OngoingActivityChipModel.Active,
+ hasSecondaryOngoingActivity =
+ chips.secondary is OngoingActivityChipModel.Active,
+ // TODO(b/364653005): Figure out the animation story here.
+ shouldAnimate = true,
+ )
}
- } else {
- listener?.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity =
- chips.primary is OngoingActivityChipModel.Active,
- hasSecondaryOngoingActivity =
- chips.secondary is OngoingActivityChipModel.Active,
- // TODO(b/364653005): Figure out the animation story here.
- shouldAnimate = true,
- )
- }
-
- viewModel.contentArea.collect { _ ->
- OngoingActivityChipBinder.resetPrimaryChipWidthRestrictions(
- primaryChipViewBinding,
- viewModel.ongoingActivityChipsLegacy.value.primary,
- )
- OngoingActivityChipBinder.resetSecondaryChipWidthRestrictions(
- secondaryChipViewBinding,
- viewModel.ongoingActivityChipsLegacy.value.secondary,
- )
- view.requestLayout()
}
+ }
+ launch {
+ viewModel.contentArea.collect { _ ->
+ OngoingActivityChipBinder.resetPrimaryChipWidthRestrictions(
+ primaryChipViewBinding,
+ viewModel.ongoingActivityChipsLegacy.value.primary,
+ )
+ OngoingActivityChipBinder.resetSecondaryChipWidthRestrictions(
+ secondaryChipViewBinding,
+ viewModel.ongoingActivityChipsLegacy.value.secondary,
+ )
+ view.requestLayout()
}
}
}
@@ -249,7 +260,7 @@ constructor(
if (SceneContainerFlag.isEnabled) {
listener?.let { listener ->
launch {
- viewModel.isHomeStatusBarAllowedByScene.collect {
+ viewModel.isHomeStatusBarAllowed.collect {
listener.onIsHomeStatusBarAllowedBySceneChanged(it)
}
}
@@ -314,48 +325,42 @@ constructor(
}
/** Bind the (legacy) single primary ongoing activity chip with the status bar visibility */
- private suspend fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
- viewModel: HomeStatusBarViewModel,
+ private fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
+ areChipsAllowed: Boolean,
primaryChipModel: OngoingActivityChipModel,
primaryChipViewBinding: OngoingActivityChipViewBinding,
) {
- viewModel.canShowOngoingActivityChips.collectLatest { visible ->
- if (!visible) {
- primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
- } else {
- when (primaryChipModel) {
- is OngoingActivityChipModel.Active -> {
- primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
- }
+ if (!areChipsAllowed) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Active -> {
+ primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
+ }
- is OngoingActivityChipModel.Inactive -> {
- primaryChipViewBinding.rootView.hide(
- state = View.GONE,
- shouldAnimateChange = primaryChipModel.shouldAnimate,
- )
- }
+ is OngoingActivityChipModel.Inactive -> {
+ primaryChipViewBinding.rootView.hide(
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
+ )
}
}
}
}
/** Bind the primary/secondary chips along with the home status bar's visibility */
- private suspend fun bindOngoingActivityChipsWithVisibility(
- viewModel: HomeStatusBarViewModel,
+ private fun bindOngoingActivityChipsWithVisibility(
+ areChipsAllowed: Boolean,
chips: MultipleOngoingActivityChipsModelLegacy,
primaryChipViewBinding: OngoingActivityChipViewBinding,
secondaryChipViewBinding: OngoingActivityChipViewBinding,
) {
- viewModel.canShowOngoingActivityChips.collectLatest { canShow ->
- if (!canShow) {
- primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
- secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
- } else {
- primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
- secondaryChipViewBinding.rootView.adjustVisibility(
- chips.secondary.toVisibilityModel()
- )
- }
+ if (!areChipsAllowed) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
+ secondaryChipViewBinding.rootView.adjustVisibility(chips.secondary.toVisibilityModel())
}
}
@@ -428,10 +433,15 @@ constructor(
// See CollapsedStatusBarFragment#hide.
private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
animate().cancel()
- if (visibility == View.INVISIBLE || visibility == View.GONE) {
+
+ if (
+ (visibility == View.INVISIBLE && state == View.INVISIBLE) ||
+ (visibility == View.GONE && state == View.GONE)
+ ) {
return
}
- if (!shouldAnimateChange) {
+ val isAlreadyHidden = visibility == View.INVISIBLE || visibility == View.GONE
+ if (!shouldAnimateChange || isAlreadyHidden) {
alpha = 0f
visibility = state
return
@@ -495,7 +505,7 @@ interface StatusBarVisibilityChangeListener {
/**
* Called when the scene state has changed such that the home status bar is newly allowed or no
- * longer allowed. See [HomeStatusBarViewModel.isHomeStatusBarAllowedByScene].
+ * longer allowed. See [HomeStatusBarViewModel.isHomeStatusBarAllowed].
*/
fun onIsHomeStatusBarAllowedBySceneChanged(isHomeStatusBarAllowedByScene: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 4189221d8a83..2f9cff46d687 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -196,13 +196,14 @@ fun StatusBarRoot(
setContent {
PlatformTheme {
- val chips by
+ val chipsVisibilityModel =
statusBarViewModel.ongoingActivityChips
- .collectAsStateWithLifecycle()
- OngoingActivityChips(
- chips = chips,
- iconViewStore = iconViewStore,
- )
+ if (chipsVisibilityModel.areChipsAllowed) {
+ OngoingActivityChips(
+ chips = chipsVisibilityModel.chips,
+ iconViewStore = iconViewStore,
+ )
+ }
}
}
}
@@ -253,7 +254,7 @@ fun StatusBarRoot(
expandedMatchesParentHeight = true
showsOnlyActiveMedia = true
falsingProtectionNeeded = false
- disablePagination = true
+ disableScrolling = true
init(MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt
new file mode 100644
index 000000000000..5cc432fc6771
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.shared.ui.model
+
+import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
+
+data class ChipsVisibilityModel(
+ val chips: MultipleOngoingActivityChipsModel,
+ /**
+ * True if the chips are allowed to be shown and false otherwise (e.g. if we're on lockscreen).
+ */
+ val areChipsAllowed: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 807e90567eb7..540babad5dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -18,8 +18,11 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.annotation.ColorInt
import android.graphics.Rect
+import android.view.Display
import android.view.View
import androidx.compose.runtime.getValue
+import com.android.app.tracing.FlowTracing.traceEach
+import com.android.app.tracing.TrackGroupUtils.trackGroup
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -41,7 +44,9 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
@@ -67,11 +72,13 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat
import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
+import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.awaitCancellation
@@ -125,7 +132,7 @@ interface HomeStatusBarViewModel : Activatable {
val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel>
/** All supported activity chips, whether they are currently active or not. */
- val ongoingActivityChips: StateFlow<MultipleOngoingActivityChipsModel>
+ val ongoingActivityChips: ChipsVisibilityModel
/**
* The multiple ongoing activity chips that should be shown on the left-hand side of the status
@@ -141,13 +148,12 @@ interface HomeStatusBarViewModel : Activatable {
val popupChips: List<PopupChipModel.Shown>
/**
- * True if the current scene can show the home status bar (aka this status bar), and false if
- * the current scene should never show the home status bar.
+ * True if the status bar should be visible.
*
* TODO(b/364360986): Once the is<SomeChildView>Visible flows are fully enabled, we shouldn't
* need this flow anymore.
*/
- val isHomeStatusBarAllowedByScene: StateFlow<Boolean>
+ val isHomeStatusBarAllowed: StateFlow<Boolean>
/** True if the home status bar is showing, and there is no HUN happening */
val canShowOngoingActivityChips: Flow<Boolean>
@@ -221,6 +227,7 @@ constructor(
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Background bgScope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
+ shadeDisplaysInteractor: Provider<ShadeDisplaysInteractor>,
) : HomeStatusBarViewModel, ExclusiveActivatable() {
private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
@@ -252,19 +259,55 @@ constructor(
override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
- override val ongoingActivityChips = ongoingActivityChipsViewModel.chips
-
override val ongoingActivityChipsLegacy = ongoingActivityChipsViewModel.chipsLegacy
override val popupChips
get() = statusBarPopupChips.shownPopupChips
- override val isHomeStatusBarAllowedByScene: StateFlow<Boolean> =
+ private val isShadeExpandedEnough =
+ // Keep the status bar visible while the shade is just starting to open, but otherwise
+ // hide it so that the status bar doesn't draw while it can't be seen.
+ // See b/394257529#comment24.
+ shadeInteractor.anyExpansion.map { it >= 0.2 }.distinctUntilChanged()
+
+ /**
+ * Whether the display of this statusbar has the shade window (that is hosting shade container
+ * and lockscreen, among other things).
+ */
+ private val isShadeWindowOnThisDisplay =
+ if (ShadeWindowGoesAround.isEnabled) {
+ shadeDisplaysInteractor.get().displayId.map { shadeDisplayId ->
+ thisDisplayId == shadeDisplayId
+ }
+ } else {
+ // Shade doesn't move anywhere, it is always on the default display.
+ flowOf(thisDisplayId == Display.DEFAULT_DISPLAY)
+ }
+
+ private val isShadeVisibleOnAnyDisplay =
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.currentOverlays.map { currentOverlays ->
+ (Overlays.NotificationsShade in currentOverlays ||
+ Overlays.QuickSettingsShade in currentOverlays)
+ }
+ } else {
+ isShadeExpandedEnough
+ }
+
+ private val isShadeVisibleOnThisDisplay: Flow<Boolean> =
+ combine(isShadeWindowOnThisDisplay, isShadeVisibleOnAnyDisplay) {
+ hasShade,
+ isShadeVisibleOnAnyDisplay ->
+ hasShade && isShadeVisibleOnAnyDisplay
+ }
+
+ private val isHomeStatusBarAllowedByScene: Flow<Boolean> =
combine(
sceneInteractor.currentScene,
- sceneInteractor.currentOverlays,
+ isShadeVisibleOnThisDisplay,
sceneContainerOcclusionInteractor.invisibleDueToOcclusion,
- ) { currentScene, currentOverlays, isOccluded ->
+ ) { currentScene, isShadeVisible, isOccluded ->
+
// All scenes have their own status bars, so we should only show the home status bar
// if we're not in a scene. There are two exceptions:
// 1) The shade (notifications or quick settings) is shown, because it has its own
@@ -272,9 +315,7 @@ constructor(
// 2) If the scene is occluded, then the occluding app needs to show the status bar.
// (Fullscreen apps actually won't show the status bar but that's handled with the
// rest of our fullscreen app logic, which lives elsewhere.)
- (currentScene == Scenes.Gone &&
- Overlays.NotificationsShade !in currentOverlays &&
- Overlays.QuickSettingsShade !in currentOverlays) || isOccluded
+ (currentScene == Scenes.Gone && !isShadeVisible) || isOccluded
}
.distinctUntilChanged()
.logDiffsForTable(
@@ -282,7 +323,6 @@ constructor(
columnName = COL_ALLOWED_BY_SCENE,
initialValue = false,
)
- .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val areNotificationsLightsOut: Flow<Boolean> =
if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
@@ -331,21 +371,27 @@ constructor(
* if we shouldn't be showing any part of the home status bar.
*/
private val isHomeScreenStatusBarAllowedLegacy: Flow<Boolean> =
- combine(
- keyguardTransitionInteractor.currentKeyguardState,
- shadeInteractor.isAnyFullyExpanded,
- ) { currentKeyguardState, isShadeExpanded ->
- (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) && !isShadeExpanded
+ combine(keyguardTransitionInteractor.currentKeyguardState, isShadeVisibleOnThisDisplay) {
+ currentKeyguardState,
+ isShadeVisibleOnThisDisplay ->
+ (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) &&
+ !isShadeVisibleOnThisDisplay
// TODO(b/364360986): Add edge cases, like secure camera launch.
}
- private val isHomeStatusBarAllowed: Flow<Boolean> =
+ // "Compat" to cover both legacy and Scene container case in one flow.
+ private val isHomeStatusBarAllowedCompat =
if (SceneContainerFlag.isEnabled) {
isHomeStatusBarAllowedByScene
} else {
isHomeScreenStatusBarAllowedLegacy
}
+ override val isHomeStatusBarAllowed =
+ isHomeStatusBarAllowedCompat
+ .traceEach(trackGroup(TRACK_GROUP, "isHomeStatusBarAllowed"), logcat = true)
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false)
+
private val shouldHomeStatusBarBeVisible =
combine(
isHomeStatusBarAllowed,
@@ -369,15 +415,6 @@ constructor(
)
.flowOn(bgDispatcher)
- private val isAnyChipVisible =
- if (StatusBarChipsModernization.isEnabled) {
- ongoingActivityChips.map { it.active.any { chip -> !chip.isHidden } }
- } else if (StatusBarNotifChips.isEnabled) {
- ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active }
- } else {
- primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Active }
- }
-
/**
* True if we need to hide the usual start side content in order to show the heads up
* notification info.
@@ -419,9 +456,43 @@ constructor(
combine(
isHomeStatusBarAllowed,
keyguardInteractor.isSecureCameraActive,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
- isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned
+ hideStartSideContentForHeadsUp,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, hideStartSideContentForHeadsUp ->
+ isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp
+ }
+
+ private val chipsVisibilityModel: Flow<ChipsVisibilityModel> =
+ combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow
+ ->
+ ChipsVisibilityModel(chips, areChipsAllowed = canShow)
+ }
+ .traceEach(trackGroup(TRACK_GROUP, "chips"), logcat = true) {
+ "Chips[allowed=${it.areChipsAllowed} numChips=${it.chips.active.size}]"
+ }
+
+ override val ongoingActivityChips: ChipsVisibilityModel by
+ hydrator.hydratedStateOf(
+ traceName = "ongoingActivityChips",
+ initialValue =
+ ChipsVisibilityModel(
+ chips = MultipleOngoingActivityChipsModel(),
+ areChipsAllowed = false,
+ ),
+ source = chipsVisibilityModel,
+ )
+
+ private val hasOngoingActivityChips =
+ if (StatusBarChipsModernization.isEnabled) {
+ chipsVisibilityModel.map { it.chips.active.any { chip -> !chip.isHidden } }
+ } else if (StatusBarNotifChips.isEnabled) {
+ ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active }
+ } else {
+ primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Active }
+ }
+
+ private val isAnyChipVisible =
+ combine(hasOngoingActivityChips, canShowOngoingActivityChips) { hasChips, canShowChips ->
+ hasChips && canShowChips
}
override val isClockVisible: Flow<VisibilityModel> =
@@ -541,6 +612,8 @@ constructor(
private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer"
private const val COL_PREFIX_SYSTEM_INFO = "systemInfo"
+ private const val TRACK_GROUP = "StatusBar"
+
fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index f9bba9d624f1..eaceb5e22535 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -26,7 +26,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
index 0a2bbe580b99..14cadd90db4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -14,7 +14,7 @@
package com.android.systemui.statusbar.policy
import android.content.res.Configuration
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index b13e01be40f7..fa022b4768fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter;
import androidx.annotation.NonNull;
+import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
@@ -56,8 +57,8 @@ public final class DeviceStateRotationLockSettingController
private int mDeviceState = -1;
@Nullable
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
- private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
- mDeviceStateRotationLockSettingsListener;
+ private DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
+ mDeviceStateAutoRotateSettingListener;
@Inject
public DeviceStateRotationLockSettingController(
@@ -83,17 +84,17 @@ public final class DeviceStateRotationLockSettingController
// is no user action.
mDeviceStateCallback = this::updateDeviceState;
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
- mDeviceStateRotationLockSettingsListener = () ->
+ mDeviceStateAutoRotateSettingListener = () ->
readPersistedSetting("deviceStateRotationLockChange", mDeviceState);
mDeviceStateRotationLockSettingsManager.registerListener(
- mDeviceStateRotationLockSettingsListener);
+ mDeviceStateAutoRotateSettingListener);
} else {
if (mDeviceStateCallback != null) {
mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
}
- if (mDeviceStateRotationLockSettingsListener != null) {
+ if (mDeviceStateAutoRotateSettingListener != null) {
mDeviceStateRotationLockSettingsManager.unregisterListener(
- mDeviceStateRotationLockSettingsListener);
+ mDeviceStateAutoRotateSettingListener);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 3cb7090ea6d4..a352982f58f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -32,6 +32,7 @@ import com.android.systemui.qs.tiles.DndTile
import com.android.systemui.qs.tiles.FlashlightTile
import com.android.systemui.qs.tiles.LocationTile
import com.android.systemui.qs.tiles.MicrophoneToggleTile
+import com.android.systemui.qs.tiles.ModesDndTile
import com.android.systemui.qs.tiles.ModesTile
import com.android.systemui.qs.tiles.UiModeNightTile
import com.android.systemui.qs.tiles.WorkModeTile
@@ -49,9 +50,13 @@ import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper
import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper
import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
@@ -132,6 +137,7 @@ interface PolicyModule {
const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle"
const val MIC_TOGGLE_TILE_SPEC = "mictoggle"
const val DND_TILE_SPEC = "dnd"
+ const val MODES_DND_TILE_SPEC = "modes_dnd"
/** Inject DndTile or ModesTile into tileMap in QSModule based on feature flag */
@Provides
@@ -146,6 +152,12 @@ interface PolicyModule {
return if (ModesUi.isEnabled) modesTile.get() else dndTile.get()
}
+ /** Inject ModesDndTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(MODES_DND_TILE_SPEC)
+ fun bindDndModeTile(tile: ModesDndTile): QSTileImpl<*> = tile
+
/** Inject flashlight config */
@Provides
@IntoMap
@@ -449,6 +461,37 @@ interface PolicyModule {
mapper,
)
else StubQSTileViewModel
+
+ @Provides
+ @IntoMap
+ @StringKey(MODES_DND_TILE_SPEC)
+ fun provideDndModeTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(MODES_DND_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_dnd_icon_off,
+ labelRes = R.string.quick_settings_dnd_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ category = TileCategory.CONNECTIVITY,
+ )
+
+ @Provides
+ @IntoMap
+ @StringKey(MODES_DND_TILE_SPEC)
+ fun provideDndModeTileViewModel(
+ factory: QSTileViewModelFactory.Static<ModesDndTileModel>,
+ mapper: ModesDndTileMapper,
+ stateInteractor: ModesDndTileDataInteractor,
+ userActionInteractor: ModesDndTileUserActionInteractor,
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(MODES_DND_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
/** Inject FlashlightTile into tileMap in QSModule */
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 2b0bf1a3d343..f1f2b88e9943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -461,6 +461,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
}
}
+ unregisterBackCallback();
if (logClose) {
mUiEventLogger.logWithInstanceId(
@@ -558,11 +559,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public void onVisibilityAggregated(boolean isVisible) {
- if (isVisible) {
- registerBackCallback();
- } else {
- unregisterBackCallback();
- }
super.onVisibilityAggregated(isVisible);
mEditText.setEnabled(isVisible && !mSending);
}
@@ -623,6 +619,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
setAttachment(mEntry.remoteInputAttachment);
updateSendButton();
+ registerBackCallback();
}
public void onNotificationUpdateOrReset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index d41ef97f4664..ab5b3499d9c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -425,7 +425,7 @@ public class SecurityControllerImpl implements SecurityController {
@Override
@Nullable
public Drawable getIcon() {
- DeprecateDpmSupervisionApis.assertInNewMode();
+ DeprecateDpmSupervisionApis.unsafeAssertInNewMode();
return isParentalControlsEnabled()
? mContext.getDrawable(R.drawable.ic_supervision)
: null;
@@ -439,7 +439,7 @@ public class SecurityControllerImpl implements SecurityController {
@Override
@Nullable
public CharSequence getLabel() {
- DeprecateDpmSupervisionApis.assertInNewMode();
+ DeprecateDpmSupervisionApis.unsafeAssertInNewMode();
return isParentalControlsEnabled()
? mContext.getString(R.string.status_bar_supervision)
: null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index d3af1e5b65fe..6d959be1c5f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import android.app.ActivityOptions
+import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
import android.app.PendingIntent
@@ -53,7 +54,6 @@ import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
-import com.android.systemui.statusbar.notification.row.MagicActionBackgroundDrawable
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
@@ -397,16 +397,21 @@ constructor(
delayOnClickListener: Boolean,
packageContext: Context,
): Button {
- val isMagicAction = Flags.notificationMagicActionsTreatment() &&
+ val isMagicAction =
+ Flags.notificationMagicActionsTreatment() &&
smartActions.fromAssistant &&
action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false)
- val layoutRes = if (isMagicAction) {
- R.layout.magic_action_button
- } else {
- R.layout.smart_action_button
- }
- return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
- as Button)
+ val layoutRes =
+ if (isMagicAction) {
+ R.layout.magic_action_button
+ } else {
+ if (notificationsRedesignTemplates()) {
+ R.layout.notification_2025_smart_action_button
+ } else {
+ R.layout.smart_action_button
+ }
+ }
+ return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) as Button)
.apply {
text = action.title
@@ -435,7 +440,6 @@ constructor(
// Mark this as an Action button
(layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
}
-
}
private fun onSmartActionClick(
@@ -499,9 +503,11 @@ constructor(
replyIndex: Int,
choice: CharSequence,
delayOnClickListener: Boolean,
- ): Button =
- (LayoutInflater.from(parent.context).inflate(R.layout.smart_reply_button, parent, false)
- as Button)
+ ): Button {
+ val layoutRes =
+ if (notificationsRedesignTemplates()) R.layout.notification_2025_smart_reply_button
+ else R.layout.smart_reply_button
+ return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) as Button)
.apply {
text = choice
val onClickListener =
@@ -531,6 +537,7 @@ constructor(
// Mark this as a Reply button
(layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
}
+ }
private fun onSmartReplyClick(
entry: NotificationEntry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
index 07bbca74e12e..2b9d39a54c44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.statusbar.policy.data.repository
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import dagger.Binds
import dagger.Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index a28d14fd908d..ed814c6b3785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -58,7 +58,7 @@ import kotlinx.coroutines.flow.stateIn
* An interactor that performs business logic related to the status and configuration of Zen Mode
* (or Do Not Disturb/DND Mode).
*/
- @SysUISingleton
+@SysUISingleton
class ZenModeInteractor
@Inject
constructor(
@@ -137,10 +137,22 @@ constructor(
)
else MutableStateFlow<ZenMode?>(null)
get() {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
return field
}
+ /**
+ * Returns the current state of the special "manual DND" mode.
+ *
+ * This should only be used when there is a strong reason to handle DND specifically (such as
+ * legacy UI pieces that haven't been updated to use modes more generally, or if the user
+ * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all
+ * other scenarios.
+ */
+ fun getDndMode(): ZenMode {
+ return zenModeRepository.getModes().single { it.isManualDnd }
+ }
+
/** Flow returning the currently active mode(s), if any. */
val activeModes: Flow<ActiveZenModes> =
modes
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 16f24f1d5821..5d7ce91b332c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -34,7 +34,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.Flags
-import com.android.systemui.qs.panels.ui.compose.PagerDots
+import com.android.systemui.common.ui.compose.PagerDots
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 2dc17f40a380..c86e00de4246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.ui.viewmodel
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 0596a80a85b0..25972ac2bedf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -185,7 +185,7 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
@Override
public void stop() {
- StatusBarConnectedDisplays.assertInNewMode();
+ StatusBarConnectedDisplays.unsafeAssertInNewMode();
try {
mWindowManager.removeView(mStatusBarWindowView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index f7688d2feab5..39afc38dad11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -24,11 +24,11 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -48,10 +48,13 @@ constructor(
displayRepository: DisplayRepository,
) :
StatusBarWindowControllerStore,
- PerDisplayStoreImpl<StatusBarWindowController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<StatusBarWindowController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController? {
diff --git a/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt b/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt
index cbd3dc73c6c0..a94d51a21af3 100644
--- a/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt
+++ b/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt
@@ -50,7 +50,9 @@ object DeprecateDpmSupervisionApis {
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
index b1b6014bfbde..9b88d439f2db 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -23,7 +23,7 @@ import android.content.pm.PackageManager
import android.telecom.TelecomManager
import android.telephony.Annotation
import android.telephony.TelephonyCallback
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9f60fe212567..48d7747d2dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -28,6 +28,7 @@ import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_DYNAMIC_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_BOTH;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
@@ -106,6 +107,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
@@ -507,18 +509,52 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+ // Condition only applies when booting to Setup Wizard.
+ // We should check if the device has specific hardware color styles, load the relative
+ // color palette and more also save the setting in our shared setting with ThemePicker.
WallpaperColors systemColor;
if (hardwareColorStyles() && !mDeviceProvisionedController.isCurrentUserSetup()) {
- Pair<Integer, Color> defaultSettings = getThemeSettingsDefaults();
- mThemeStyle = defaultSettings.first;
- Color seedColor = defaultSettings.second;
+ HardwareDefaultSetting defaultSettings = getThemeSettingsDefaults();
+ mThemeStyle = defaultSettings.style;
// we only use the first color anyway, so we can pass only the single color we have
systemColor = new WallpaperColors(
- /*primaryColor*/ seedColor,
- /*secondaryColor*/ seedColor,
- /*tertiaryColor*/ seedColor
+ /*primaryColor*/ defaultSettings.seedColor,
+ /*secondaryColor*/ defaultSettings.seedColor,
+ /*tertiaryColor*/ defaultSettings.seedColor
+ );
+
+ /* We update the json in THEME_CUSTOMIZATION_OVERLAY_PACKAGES to reflect the preset. */
+ final int currentUser = mUserTracker.getUserId();
+ final String overlayPackageJson = Objects.requireNonNullElse(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ currentUser),
+ "{}"
);
+
+ try {
+ JSONObject object = new JSONObject(overlayPackageJson);
+ String seedColorStr = Integer.toHexString(defaultSettings.seedColor.toArgb());
+ object.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColorStr);
+ object.put(OVERLAY_CATEGORY_ACCENT_COLOR, seedColorStr);
+ object.put(OVERLAY_COLOR_SOURCE, defaultSettings.colorSource);
+ object.put(OVERLAY_CATEGORY_THEME_STYLE, Style.toString(mThemeStyle));
+
+ mSecureSettings.putStringForUser(
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, object.toString(),
+ UserHandle.USER_CURRENT);
+
+ Log.d(TAG, "Hardware Color Defaults loaded: " + object.toString());
+
+ } catch (JSONException e) {
+ Log.d(TAG, "Failed to store hardware color defaults in "
+ + "THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
+ }
+
+ // now we have to update
+
} else {
systemColor = mWallpaperManager.getWallpaperColors(
getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
@@ -863,7 +899,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
try {
JSONObject object = new JSONObject(overlayPackageJson);
style = Style.valueOf(
- object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE));
+ object.getString(OVERLAY_CATEGORY_THEME_STYLE));
if (!validStyles.contains(style)) {
style = Style.TONAL_SPOT;
}
@@ -899,26 +935,29 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
}
String deviceColorPropertyValue = mSystemPropertiesHelper.get(deviceColorProperty);
- Pair<Integer, String> selectedTheme = themeMap.get(deviceColorPropertyValue);
- if (selectedTheme == null) {
+ Pair<Integer, String> styleAndSource = themeMap.get(deviceColorPropertyValue);
+ if (styleAndSource == null) {
Log.d(TAG, "Sysprop `" + deviceColorProperty + "` of value '" + deviceColorPropertyValue
+ "' not found in theming_defaults: " + Arrays.toString(themeData));
- selectedTheme = fallbackTheme;
+ styleAndSource = fallbackTheme;
}
- return selectedTheme;
+ return styleAndSource;
+ }
+
+ record HardwareDefaultSetting(Color seedColor, @Style.Type int style, String colorSource) {
}
@VisibleForTesting
- protected Pair<Integer, Color> getThemeSettingsDefaults() {
+ protected HardwareDefaultSetting getThemeSettingsDefaults() {
- Pair<Integer, String> selectedTheme = getHardwareColorSetting();
+ Pair<Integer, String> styleAndSource = getHardwareColorSetting();
// Last fallback color
Color defaultSeedColor = Color.valueOf(GOOGLE_BLUE);
// defaultColor will come from wallpaper or be parsed from a string
- boolean isWallpaper = selectedTheme.second.equals(COLOR_SOURCE_HOME);
+ boolean isWallpaper = styleAndSource.second.equals(COLOR_SOURCE_HOME);
if (isWallpaper) {
WallpaperColors wallpaperColors = mWallpaperManager.getWallpaperColors(
@@ -932,16 +971,17 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
defaultSeedColor.toArgb()));
} else {
try {
- defaultSeedColor = Color.valueOf(Color.parseColor(selectedTheme.second));
+ defaultSeedColor = Color.valueOf(Color.parseColor(styleAndSource.second));
Log.d(TAG, "Default seed color read from resource: " + Integer.toHexString(
defaultSeedColor.toArgb()));
} catch (IllegalArgumentException e) {
- Log.e(TAG, "Error parsing color: " + selectedTheme.second, e);
+ Log.e(TAG, "Error parsing color: " + styleAndSource.second, e);
// defaultSeedColor remains unchanged in this case
}
}
- return new Pair<>(selectedTheme.first, defaultSeedColor);
+ return new HardwareDefaultSetting(defaultSeedColor, styleAndSource.first,
+ isWallpaper ? COLOR_SOURCE_HOME : COLOR_SOURCE_PRESET);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt
new file mode 100644
index 000000000000..c11e4c507914
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects;
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.view.Gravity
+import android.view.WindowInsets
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
+import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot
+import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@SysUISingleton
+class TopLevelWindowEffects @Inject constructor(
+ @Application private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val windowManager: ViewCaptureAwareWindowManager,
+ private val squeezeEffectInteractor: SqueezeEffectInteractor,
+ private val keyEventInteractor: KeyEventInteractor,
+ private val viewModelFactory: SqueezeEffectViewModel.Factory
+) : CoreStartable {
+
+ override fun start() {
+ applicationScope.launch {
+ var root: EffectsWindowRoot? = null
+ squeezeEffectInteractor.isSqueezeEffectEnabled.collectLatest { enabled ->
+ // TODO: move window ops to a separate UI thread
+ if (enabled) {
+ keyEventInteractor.isPowerButtonDown.collectLatest { down ->
+ // TODO: ignore new window creation when ignoring short power press duration
+ if (down && root == null) {
+ root = EffectsWindowRoot(
+ context = context,
+ viewModelFactory = viewModelFactory,
+ onEffectFinished = {
+ if (root?.isAttachedToWindow == true) {
+ windowManager.removeView(root)
+ root = null
+ }
+ }
+ )
+ root?.let {
+ windowManager.addView(it, getWindowManagerLayoutParams())
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun getWindowManagerLayoutParams(): WindowManager.LayoutParams {
+ val lp = WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSPARENT
+ )
+
+ lp.privateFlags = lp.privateFlags or
+ (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ or WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+ or WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED
+ or WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
+ or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+
+ lp.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+
+ lp.title = "TopLevelWindowEffects"
+ lp.fitInsetsTypes = WindowInsets.Type.systemOverlays()
+ lp.gravity = Gravity.TOP
+
+ return lp
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt
new file mode 100644
index 000000000000..5a2af9228771
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.dagger
+
+import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepository
+import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface SqueezeEffectRepositoryModule {
+
+ @Binds
+ fun squeezeEffectRepository(
+ squeezeEffectRepositoryImpl: SqueezeEffectRepositoryImpl
+ ) : SqueezeEffectRepository
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt
new file mode 100644
index 000000000000..6fbfedc94774
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.topwindoweffects.TopLevelWindowEffects
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface TopLevelWindowEffectsModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(TopLevelWindowEffects::class)
+ fun bindTopLevelWindowEffectsCoreStartable(impl: TopLevelWindowEffects): CoreStartable
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt
new file mode 100644
index 000000000000..9e0b25633d5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.data.repository
+
+import kotlinx.coroutines.flow.Flow
+
+interface SqueezeEffectRepository {
+ val isSqueezeEffectEnabled: Flow<Boolean>
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt
new file mode 100644
index 000000000000..9e0feed35016
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS
+import com.android.internal.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.Flags
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+@SysUISingleton
+class SqueezeEffectRepositoryImpl @Inject constructor(
+ @Background private val bgHandler: Handler?,
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val globalSettings: GlobalSettings
+) : SqueezeEffectRepository {
+
+ override val isSqueezeEffectEnabled: Flow<Boolean> = conflatedCallbackFlow {
+ val observer = object : ContentObserver(bgHandler) {
+ override fun onChange(selfChange: Boolean) {
+ trySendWithFailureLogging(squeezeEffectEnabled, TAG,
+ "updated isSqueezeEffectEnabled")
+ }
+ }
+ trySendWithFailureLogging(squeezeEffectEnabled, TAG, "init isSqueezeEffectEnabled")
+ globalSettings.registerContentObserverAsync(POWER_BUTTON_LONG_PRESS, observer)
+ awaitClose { globalSettings.unregisterContentObserverAsync(observer) }
+ }.flowOn(bgCoroutineContext)
+
+ private val squeezeEffectEnabled
+ get() = Flags.enableLppSqueezeEffect() && globalSettings.getInt(
+ POWER_BUTTON_LONG_PRESS, R.integer.config_longPressOnPowerBehavior
+ ) == 5 // 5 corresponds to launch assistant in config_longPressOnPowerBehavior
+
+ companion object {
+ private const val TAG = "SqueezeEffectRepository"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt
new file mode 100644
index 000000000000..879fde769aee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class SqueezeEffectInteractor @Inject constructor(
+ squeezeEffectRepository: SqueezeEffectRepository
+) {
+ val isSqueezeEffectEnabled = squeezeEffectRepository.isSqueezeEffectEnabled
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt
new file mode 100644
index 000000000000..61448f45c0e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.ui.compose
+
+import android.annotation.SuppressLint
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.AbstractComposeView
+import com.android.systemui.compose.ComposeInitializer
+import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
+
+@SuppressLint("ViewConstructor")
+class EffectsWindowRoot(
+ context: Context,
+ private val onEffectFinished: () -> Unit,
+ private val viewModelFactory: SqueezeEffectViewModel.Factory
+) : AbstractComposeView(context) {
+
+ override fun onAttachedToWindow() {
+ ComposeInitializer.onAttachedToWindow(this)
+ super.onAttachedToWindow()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ ComposeInitializer.onDetachedFromWindow(this)
+ }
+
+ @Composable
+ override fun Content() {
+ SqueezeEffect(
+ viewModelFactory = viewModelFactory,
+ onEffectFinished = onEffectFinished
+ )
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt
new file mode 100644
index 000000000000..9809b0592dcf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.ui.compose
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.withTransform
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.VectorPainter
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.unit.dp
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.res.R
+import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
+
+private val SqueezeEffectMaxThickness = 12.dp
+private val SqueezeColor = Color.Black
+
+@Composable
+fun SqueezeEffect(
+ viewModelFactory: SqueezeEffectViewModel.Factory,
+ onEffectFinished: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val viewModel = rememberViewModel(traceName = "SqueezeEffect") { viewModelFactory.create() }
+ val down = viewModel.isPowerButtonPressed
+ val longPressed = viewModel.isPowerButtonLongPressed
+ // TODO: Choose the correct resource based on primary / secondary display
+ val top = rememberVectorPainter(ImageVector.vectorResource(R.drawable.rounded_corner_top))
+ val bottom = rememberVectorPainter(ImageVector.vectorResource(R.drawable.rounded_corner_bottom))
+
+ val squeezeProgress by animateFloatAsState(
+ targetValue =
+ if (down && !longPressed) {
+ 1f
+ } else {
+ 0f
+ },
+ animationSpec = tween(durationMillis = 400),
+ finishedListener = { onEffectFinished() }
+ )
+
+ Canvas(modifier = modifier.fillMaxSize()) {
+ if (squeezeProgress <= 0) {
+ return@Canvas
+ }
+
+ val squeezeThickness = SqueezeEffectMaxThickness.toPx() * squeezeProgress
+
+ drawRect(color = SqueezeColor, size = Size(size.width, squeezeThickness))
+
+ drawRect(
+ color = SqueezeColor,
+ topLeft = Offset(0f, size.height - squeezeThickness),
+ size = Size(size.width, squeezeThickness)
+ )
+
+ drawRect(color = SqueezeColor, size = Size(squeezeThickness, size.height))
+
+ drawRect(
+ color = SqueezeColor,
+ topLeft = Offset(size.width - squeezeThickness, 0f),
+ size = Size(squeezeThickness, size.height)
+ )
+
+ drawTransform(
+ dx = squeezeThickness,
+ dy = squeezeThickness,
+ rotation = 0f,
+ corner = top
+ )
+
+ drawTransform(
+ dx = size.width - squeezeThickness,
+ dy = squeezeThickness,
+ rotation = 90f,
+ corner = top
+ )
+
+ drawTransform(
+ dx = squeezeThickness,
+ dy = size.height - squeezeThickness,
+ rotation = 270f,
+ corner = bottom
+ )
+
+ drawTransform(
+ dx = size.width - squeezeThickness,
+ dy = size.height - squeezeThickness,
+ rotation = 180f,
+ corner = bottom
+ )
+ }
+}
+
+private fun DrawScope.drawTransform(
+ dx: Float,
+ dy: Float,
+ rotation: Float = 0f,
+ corner: VectorPainter,
+) {
+ withTransform(transformBlock = {
+ transform(matrix = Matrix().apply {
+ translate(dx, dy)
+ if (rotation != 0f) {
+ rotateZ(rotation)
+ }
+ })
+ }) {
+ with(corner) {
+ draw(size = intrinsicSize)
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt
new file mode 100644
index 000000000000..1cab327740ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class SqueezeEffectViewModel
+@AssistedInject
+constructor(
+ keyEventInteractor: KeyEventInteractor
+) : ExclusiveActivatable() {
+ private val hydrator = Hydrator("SqueezeEffectViewModel.hydrator")
+
+ val isPowerButtonPressed: Boolean by hydrator.hydratedStateOf(
+ traceName = "isPowerButtonPressed",
+ initialValue = false,
+ source = keyEventInteractor.isPowerButtonDown
+ )
+
+ val isPowerButtonLongPressed: Boolean by hydrator.hydratedStateOf(
+ traceName = "isPowerButtonLongPressed",
+ initialValue = false,
+ source = keyEventInteractor.isPowerButtonLongPressed
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): SqueezeEffectViewModel
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index eecea9228ea3..2f8da2eae68a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -84,7 +84,7 @@ fun TutorialSelectionScreen(
),
) {
val isCompactWindow = hasCompactWindowSize()
- val padding = if (isCompactWindow) 24.dp else 60.dp
+ val padding = if (isCompactWindow) 24.dp else 48.dp
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
@@ -121,7 +121,7 @@ fun TutorialSelectionScreen(
// because other composables have weight 1, Done button will be positioned first
DoneButton(
onDoneButtonClicked = onDoneButtonClicked,
- modifier = Modifier.padding(horizontal = padding),
+ modifier = Modifier.padding(start = padding, top = 0.dp, end = padding, bottom = 32.dp),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
index fbbd2b9c5de8..e47d74ec9412 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
@@ -16,7 +16,7 @@
package com.android.systemui.unfold.data.repository
import androidx.annotation.FloatRange
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
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 c960b5525d96..05b2e0d1423e 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
@@ -33,7 +33,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index bcbd679b35eb..412161cf98bc 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -23,7 +23,7 @@ import android.os.UserManager
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
index 3bd8af690763..6657c428a594 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
@@ -20,6 +20,7 @@ import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -31,7 +32,14 @@ class UserLockedInteractor
constructor(
@Background val backgroundDispatcher: CoroutineDispatcher,
val userRepository: UserRepository,
+ val selectedUserInteractor: SelectedUserInteractor,
) {
+ /** Whether the current user is unlocked */
+ val currentUserUnlocked: Flow<Boolean> =
+ selectedUserInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+ isUserUnlocked(user.userHandle)
+ }
+
fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> =
userRepository.isUserUnlocked(userHandle).flowOn(backgroundDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt
index 31a8d864de95..9937eeb29151 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt
@@ -19,7 +19,7 @@ import android.content.ContentResolver
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
index 80ccd646f6be..d4eabb9264e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.statusbar.policy.BatteryController
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt
index 7a2f9b24700f..837bbea9cc3c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ManagedProfileControllerExt.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.statusbar.phone.ManagedProfileController
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
index ee00e8b04ef1..02012ede697b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.qs.ReduceBrightColorsController
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
index 22cc8dd7745d..a914c86da0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util.kotlin
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.statusbar.policy.RotationLockController
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
index 94b3fd244a92..6cdc94251d21 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/SettingsForUserRepository.kt
@@ -47,6 +47,11 @@ abstract class SettingsForUserRepository(
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
+ fun intSettingForUser(userId: Int, name: String, defaultValue: Int = 0): Flow<Int> =
+ settingObserver(name, userId) { userSettings.getIntForUser(name, defaultValue, userId) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
return userSettings
.observerFlow(userId, name)
@@ -63,4 +68,14 @@ abstract class SettingsForUserRepository(
userSettings.getBoolForUser(name, defaultValue, userId)
}
}
+
+ suspend fun setIntForUser(userId: Int, name: String, value: Int) {
+ withContext(backgroundContext) { userSettings.putIntForUser(name, value, userId) }
+ }
+
+ suspend fun getIntForUser(userId: Int, name: String, defaultValue: Int = 0): Int {
+ return withContext(backgroundContext) {
+ userSettings.getIntForUser(name, defaultValue, userId)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index bb8fe46be5b0..3ac6c7bc0c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -28,7 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.volume.Events
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.factory.VolumeDialogComponentFactory
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import javax.inject.Inject
import kotlinx.coroutines.awaitCancellation
@@ -37,7 +37,7 @@ class VolumeDialog
@Inject
constructor(
@Application context: Context,
- private val componentFactory: VolumeDialogComponent.Factory,
+ private val componentFactory: VolumeDialogComponentFactory,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
) : ComponentDialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
index 434f6b567048..36b2b48ece21 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dialog.dagger
+import com.android.systemui.volume.dialog.dagger.factory.VolumeDialogComponentFactory
import com.android.systemui.volume.dialog.dagger.module.VolumeDialogModule
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
@@ -38,9 +39,9 @@ interface VolumeDialogComponent {
fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory
@Subcomponent.Factory
- interface Factory {
+ interface Factory : VolumeDialogComponentFactory {
- fun create(
+ override fun create(
/**
* Provides a coroutine scope to use inside [VolumeDialogScope].
* [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt
new file mode 100644
index 000000000000..d909f26dd1dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/factory/VolumeDialogComponentFactory.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.volume.dialog.dagger.factory
+
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import kotlinx.coroutines.CoroutineScope
+
+/** Common interface for all dagger Subcomponent.Factory providing [VolumeDialogComponent]. */
+interface VolumeDialogComponentFactory {
+
+ fun create(scope: CoroutineScope): VolumeDialogComponent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
index 7b08317d5265..fcf4d110f269 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
@@ -16,10 +16,12 @@
package com.android.systemui.volume.dialog.dagger.module
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository
import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepositoryImpl
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
import com.android.systemui.volume.dialog.ui.binder.ViewBinder
import dagger.Binds
@@ -27,7 +29,7 @@ import dagger.Module
import dagger.Provides
/** Dagger module for volume dialog code in the volume package */
-@Module
+@Module(subcomponents = [VolumeDialogSliderComponent::class])
interface VolumeDialogModule {
@Binds
@@ -38,6 +40,7 @@ interface VolumeDialogModule {
companion object {
@Provides
+ @VolumeDialog
fun provideViewBinders(
slidersViewBinder: VolumeDialogSlidersViewBinder,
ringerViewBinder: VolumeDialogRingerViewBinder,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
index 547c51d1cefd..35752ae08260 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.dialog.dagger.module
import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.dagger.factory.VolumeDialogComponentFactory
import com.android.systemui.volume.dialog.shared.model.CsdWarningConfigModel
import com.android.systemui.volume.dialog.utils.VolumeTracer
import com.android.systemui.volume.dialog.utils.VolumeTracerImpl
@@ -27,6 +28,11 @@ import dagger.Provides
@Module(subcomponents = [VolumeDialogComponent::class])
interface VolumeDialogPluginModule {
+ @Binds
+ fun bindVolumeDialogComponentFactory(
+ factory: VolumeDialogComponent.Factory
+ ): VolumeDialogComponentFactory
+
@Binds fun bindVolumeTracer(volumeTracer: VolumeTracerImpl): VolumeTracer
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index 577e47bb3b83..d0ed24d0b86d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.sliders.dagger
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import dagger.BindsInstance
import dagger.Subcomponent
@@ -34,6 +35,8 @@ interface VolumeDialogSliderComponent {
fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder
+ fun sliderViewModel(): VolumeDialogSliderViewModel
+
@Subcomponent.Factory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 5de8fe54505f..11d9df4294c0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -37,6 +37,7 @@ import com.android.systemui.common.ui.view.onApplyWindowInsets
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.awaitCancellationThenDispose
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
@@ -72,7 +73,7 @@ constructor(
private val viewModel: VolumeDialogViewModel,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
- private val viewBinders: List<@JvmSuppressWildcards ViewBinder>,
+ @VolumeDialog private val viewBinders: List<@JvmSuppressWildcards ViewBinder>,
) {
private val halfOpenedOffsetPx: Float =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
index 485f4b5cbfd7..4919139385ab 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
@@ -29,6 +29,6 @@ class VolumePanelFlag @Inject constructor() {
}
fun assertNewVolumePanel() {
- RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
+ RefactorFlagUtils.unsafeAssertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
index 720d5507e224..f6582a005035 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
@@ -72,10 +72,10 @@ fun Slider(
valueRange: ClosedFloatingPointRange<Float>,
onValueChanged: (Float) -> Unit,
onValueChangeFinished: ((Float) -> Unit)?,
- stepDistance: Float,
isEnabled: Boolean,
accessibilityParams: AccessibilityParams,
modifier: Modifier = Modifier,
+ stepDistance: Float = 0f,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
haptics: Haptics = Haptics.Disabled,
@@ -83,7 +83,7 @@ fun Slider(
isReverseDirection: Boolean = false,
track: (@Composable (SliderState) -> Unit)? = null,
) {
- require(stepDistance > 0) { "stepDistance must be positive" }
+ require(stepDistance >= 0) { "stepDistance must not be negative" }
val coroutineScope = rememberCoroutineScope()
val snappedValue = snapValue(value, valueRange, stepDistance)
val hapticsViewModel = haptics.createViewModel(snappedValue, valueRange, interactionSource)
@@ -192,16 +192,20 @@ private fun AccessibilityParams.createSemantics(
setProgress { targetValue ->
val targetDirection =
when {
- targetValue > value -> 1
- targetValue < value -> -1
- else -> 0
+ targetValue > value -> 1f
+ targetValue < value -> -1f
+ else -> 0f
+ }
+ val offset =
+ if (stepDistance > 0) {
+ // advance to the next step when stepDistance is > 0
+ targetDirection * stepDistance
+ } else {
+ // advance to the desired value otherwise
+ targetValue - value
}
- val newValue =
- (value + targetDirection * stepDistance).coerceIn(
- valueRange.start,
- valueRange.endInclusive,
- )
+ val newValue = (value + offset).coerceIn(valueRange.start, valueRange.endInclusive)
onValueChanged(newValue)
true
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
index 594c5526dca9..7e9ebd218787 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -24,7 +24,7 @@ import android.service.quickaccesswallet.QuickAccessWalletClient
import android.service.quickaccesswallet.WalletCard
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index 300a7e070b6c..72bc9f690364 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -36,6 +36,8 @@ import kotlinx.coroutines.flow.flowOf
@SysUISingleton
class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
+ override val lockscreenWallpaperInfo: StateFlow<WallpaperInfo?> =
+ MutableStateFlow(null).asStateFlow()
override val wallpaperSupportsAmbientMode = flowOf(false)
override var rootView: View? = null
override val shouldSendFocalArea: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index b07342c4c76d..d314f7669421 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -18,6 +18,8 @@ package com.android.systemui.wallpapers.data.repository
import android.app.WallpaperInfo
import android.app.WallpaperManager
+import android.app.WallpaperManager.FLAG_LOCK
+import android.app.WallpaperManager.FLAG_SYSTEM
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -30,9 +32,11 @@ import android.util.Log
import android.view.View
import com.android.internal.R
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R as SysUIR
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.shared.Flags.extendedWallpaperEffects
import com.android.systemui.user.data.model.SelectedUserModel
@@ -63,6 +67,12 @@ interface WallpaperRepository {
/** Emits the current user's current wallpaper. */
val wallpaperInfo: StateFlow<WallpaperInfo?>
+ /**
+ * Emits the current user's lockscreen wallpaper. This will emit the same value as
+ * [wallpaperInfo] if the wallpaper is shared between home and lock screen.
+ */
+ val lockscreenWallpaperInfo: StateFlow<WallpaperInfo?>
+
/** Emits true if the current user's current wallpaper supports ambient mode. */
val wallpaperSupportsAmbientMode: Flow<Boolean>
@@ -81,13 +91,14 @@ interface WallpaperRepository {
class WallpaperRepositoryImpl
@Inject
constructor(
- @Background scope: CoroutineScope,
+ @Background private val scope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
private val wallpaperManager: WallpaperManager,
private val context: Context,
private val secureSettings: SecureSettings,
+ @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
) : WallpaperRepository {
private val wallpaperChanged: Flow<Unit> =
broadcastDispatcher
@@ -106,26 +117,19 @@ constructor(
// Only update the wallpaper status once the user selection has finished.
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
- override val wallpaperInfo: StateFlow<WallpaperInfo?> =
- if (!wallpaperManager.isWallpaperSupported) {
- MutableStateFlow(null).asStateFlow()
- } else {
- combine(wallpaperChanged, selectedUser, ::Pair)
- .mapLatestConflated { (_, selectedUser) -> getWallpaper(selectedUser) }
- .stateIn(
- scope,
- // Always be listening for wallpaper changes.
- SharingStarted.Eagerly,
- // The initial value is null, but it should get updated pretty quickly because
- // the `combine` should immediately kick off a fetch.
- initialValue = null,
- )
- }
-
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> = getWallpaperInfo(FLAG_SYSTEM)
+ override val lockscreenWallpaperInfo: StateFlow<WallpaperInfo?> = getWallpaperInfo(FLAG_LOCK)
override val wallpaperSupportsAmbientMode: Flow<Boolean> =
- secureSettings
- .observerFlow(UserHandle.USER_ALL, Settings.Secure.DOZE_ALWAYS_ON_WALLPAPER_ENABLED)
- .onStart { emit(Unit) }
+ combine(
+ secureSettings
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.DOZE_ALWAYS_ON_WALLPAPER_ENABLED,
+ )
+ .onStart { emit(Unit) },
+ configurationInteractor.onAnyConfigurationChange,
+ ::Pair,
+ )
.map {
val userEnabled =
secureSettings.getInt(Settings.Secure.DOZE_ALWAYS_ON_WALLPAPER_ENABLED, 1) == 1
@@ -172,7 +176,7 @@ constructor(
}
override val shouldSendFocalArea =
- wallpaperInfo
+ lockscreenWallpaperInfo
.map {
val focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
val shouldSendNotificationLayout = it?.component?.className == focalAreaTarget
@@ -184,12 +188,35 @@ constructor(
initialValue = extendedWallpaperEffects(),
)
- private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
+ private suspend fun getWallpaper(
+ selectedUser: SelectedUserModel,
+ which: Int = FLAG_SYSTEM,
+ ): WallpaperInfo? {
return withContext(bgDispatcher) {
- wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
+ if (which == FLAG_LOCK && wallpaperManager.lockScreenWallpaperExists()) {
+ wallpaperManager.getWallpaperInfo(FLAG_LOCK, selectedUser.userInfo.id)
+ } else {
+ wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
+ }
}
}
+ private fun getWallpaperInfo(which: Int): StateFlow<WallpaperInfo?> =
+ if (!wallpaperManager.isWallpaperSupported) {
+ MutableStateFlow(null).asStateFlow()
+ } else {
+ combine(wallpaperChanged, selectedUser, ::Pair)
+ .mapLatestConflated { (_, selectedUser) -> getWallpaper(selectedUser, which) }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ SharingStarted.Eagerly,
+ // The initial value is null, but it should get updated pretty quickly because
+ // the `combine` should immediately kick off a fetch.
+ initialValue = null,
+ )
+ }
+
companion object {
private val TAG = WallpaperRepositoryImpl::class.simpleName
private val DEBUG = true
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index feaf1a630a53..8e713495902a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -302,7 +302,7 @@ public final class WMShell implements
.commitUpdate(mDisplayTracker.getDefaultDisplayId());
}
});
- mSysUiState.addCallback(sysUiStateFlag -> {
+ mSysUiState.addCallback((sysUiStateFlag, displayId) -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
});
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
index aa8044515ea2..aa8044515ea2 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
index aa8044515ea2..aa8044515ea2 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
index 561961145ca1..5476160df156 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
@@ -371,14 +371,5 @@
0
]
}
- ],
- "\/\/metadata": {
- "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json",
- "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring",
- "testClassName": "TransitionAnimatorTest",
- "testMethodName": "backgroundAnimation_whenReturning[true]",
- "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
- "result": "FAILED",
- "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4"
- }
+ ]
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
index 7abff2c74531..5476160df156 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -371,14 +371,5 @@
0
]
}
- ],
- "\/\/metadata": {
- "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json",
- "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring",
- "testClassName": "TransitionAnimatorTest",
- "testMethodName": "backgroundAnimation_whenLaunching[true]",
- "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
- "result": "FAILED",
- "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4"
- }
+ ]
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..aa8044515ea2
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..aa8044515ea2
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..5476160df156
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 45,
+ 126,
+ 190,
+ 228,
+ 246,
+ 253,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..5476160df156
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 45,
+ 126,
+ 190,
+ 228,
+ 246,
+ 253,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..aa8044515ea2
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..aa8044515ea2
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..5476160df156
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 45,
+ 126,
+ 190,
+ 228,
+ 246,
+ 253,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..5476160df156
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 45,
+ 126,
+ 190,
+ 228,
+ 246,
+ 253,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..aa8044515ea2
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..aa8044515ea2
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..5476160df156
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 45,
+ 126,
+ 190,
+ 228,
+ 246,
+ 253,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..5476160df156
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 45,
+ 126,
+ 190,
+ 228,
+ 246,
+ 253,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
index 7f623575fef4..7f623575fef4 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..41a46df55e38
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
index 825190ba7a32..848c7d460d3b 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
@@ -371,14 +371,5 @@
0
]
}
- ],
- "\/\/metadata": {
- "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json",
- "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring",
- "testClassName": "TransitionAnimatorTest",
- "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]",
- "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
- "result": "FAILED",
- "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4"
- }
+ ]
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
index 63c263175122..848c7d460d3b 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -371,14 +371,5 @@
0
]
}
- ],
- "\/\/metadata": {
- "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json",
- "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring",
- "testClassName": "TransitionAnimatorTest",
- "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]",
- "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
- "result": "FAILED",
- "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4"
- }
+ ]
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
index 98005c53f6e0..98005c53f6e0 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..98005c53f6e0
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..848c7d460d3b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..848c7d460d3b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..7f623575fef4
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..7f623575fef4
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 96,
+ 153,
+ 192,
+ 220,
+ 238,
+ 249,
+ 254,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..848c7d460d3b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..848c7d460d3b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenLaunching_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..98005c53f6e0
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..98005c53f6e0
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withAnimator_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,492 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 99,
+ "top": 296,
+ "right": 202,
+ "bottom": 404
+ },
+ {
+ "left": 95,
+ "top": 283,
+ "right": 207,
+ "bottom": 417
+ },
+ {
+ "left": 86,
+ "top": 256,
+ "right": 219,
+ "bottom": 443
+ },
+ {
+ "left": 68,
+ "top": 198,
+ "right": 243,
+ "bottom": 499
+ },
+ {
+ "left": 39,
+ "top": 110,
+ "right": 278,
+ "bottom": 584
+ },
+ {
+ "left": 26,
+ "top": 74,
+ "right": 292,
+ "bottom": 618
+ },
+ {
+ "left": 19,
+ "top": 55,
+ "right": 299,
+ "bottom": 637
+ },
+ {
+ "left": 15,
+ "top": 42,
+ "right": 304,
+ "bottom": 649
+ },
+ {
+ "left": 12,
+ "top": 33,
+ "right": 307,
+ "bottom": 658
+ },
+ {
+ "left": 9,
+ "top": 27,
+ "right": 310,
+ "bottom": 664
+ },
+ {
+ "left": 7,
+ "top": 21,
+ "right": 312,
+ "bottom": 669
+ },
+ {
+ "left": 6,
+ "top": 17,
+ "right": 314,
+ "bottom": 674
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 4,
+ "top": 10,
+ "right": 316,
+ "bottom": 680
+ },
+ {
+ "left": 3,
+ "top": 8,
+ "right": 317,
+ "bottom": 682
+ },
+ {
+ "left": 2,
+ "top": 6,
+ "right": 318,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.865689,
+ "top_left_y": 9.865689,
+ "top_right_x": 9.865689,
+ "top_right_y": 9.865689,
+ "bottom_right_x": 19.731379,
+ "bottom_right_y": 19.731379,
+ "bottom_left_x": 19.731379,
+ "bottom_left_y": 19.731379
+ },
+ {
+ "top_left_x": 9.419104,
+ "top_left_y": 9.419104,
+ "top_right_x": 9.419104,
+ "top_right_y": 9.419104,
+ "bottom_right_x": 18.838207,
+ "bottom_right_y": 18.838207,
+ "bottom_left_x": 18.838207,
+ "bottom_left_y": 18.838207
+ },
+ {
+ "top_left_x": 8.533693,
+ "top_left_y": 8.533693,
+ "top_right_x": 8.533693,
+ "top_right_y": 8.533693,
+ "bottom_right_x": 17.067387,
+ "bottom_right_y": 17.067387,
+ "bottom_left_x": 17.067387,
+ "bottom_left_y": 17.067387
+ },
+ {
+ "top_left_x": 6.5919456,
+ "top_left_y": 6.5919456,
+ "top_right_x": 6.5919456,
+ "top_right_y": 6.5919456,
+ "bottom_right_x": 13.183891,
+ "bottom_right_y": 13.183891,
+ "bottom_left_x": 13.183891,
+ "bottom_left_y": 13.183891
+ },
+ {
+ "top_left_x": 3.6674318,
+ "top_left_y": 3.6674318,
+ "top_right_x": 3.6674318,
+ "top_right_y": 3.6674318,
+ "bottom_right_x": 7.3348637,
+ "bottom_right_y": 7.3348637,
+ "bottom_left_x": 7.3348637,
+ "bottom_left_y": 7.3348637
+ },
+ {
+ "top_left_x": 2.4832253,
+ "top_left_y": 2.4832253,
+ "top_right_x": 2.4832253,
+ "top_right_y": 2.4832253,
+ "bottom_right_x": 4.9664507,
+ "bottom_right_y": 4.9664507,
+ "bottom_left_x": 4.9664507,
+ "bottom_left_y": 4.9664507
+ },
+ {
+ "top_left_x": 1.8252907,
+ "top_left_y": 1.8252907,
+ "top_right_x": 1.8252907,
+ "top_right_y": 1.8252907,
+ "bottom_right_x": 3.6505814,
+ "bottom_right_y": 3.6505814,
+ "bottom_left_x": 3.6505814,
+ "bottom_left_y": 3.6505814
+ },
+ {
+ "top_left_x": 1.4077549,
+ "top_left_y": 1.4077549,
+ "top_right_x": 1.4077549,
+ "top_right_y": 1.4077549,
+ "bottom_right_x": 2.8155098,
+ "bottom_right_y": 2.8155098,
+ "bottom_left_x": 2.8155098,
+ "bottom_left_y": 2.8155098
+ },
+ {
+ "top_left_x": 1.1067667,
+ "top_left_y": 1.1067667,
+ "top_right_x": 1.1067667,
+ "top_right_y": 1.1067667,
+ "bottom_right_x": 2.2135334,
+ "bottom_right_y": 2.2135334,
+ "bottom_left_x": 2.2135334,
+ "bottom_left_y": 2.2135334
+ },
+ {
+ "top_left_x": 0.88593864,
+ "top_left_y": 0.88593864,
+ "top_right_x": 0.88593864,
+ "top_right_y": 0.88593864,
+ "bottom_right_x": 1.7718773,
+ "bottom_right_y": 1.7718773,
+ "bottom_left_x": 1.7718773,
+ "bottom_left_y": 1.7718773
+ },
+ {
+ "top_left_x": 0.7069988,
+ "top_left_y": 0.7069988,
+ "top_right_x": 0.7069988,
+ "top_right_y": 0.7069988,
+ "bottom_right_x": 1.4139977,
+ "bottom_right_y": 1.4139977,
+ "bottom_left_x": 1.4139977,
+ "bottom_left_y": 1.4139977
+ },
+ {
+ "top_left_x": 0.55613136,
+ "top_left_y": 0.55613136,
+ "top_right_x": 0.55613136,
+ "top_right_y": 0.55613136,
+ "bottom_right_x": 1.1122627,
+ "bottom_right_y": 1.1122627,
+ "bottom_left_x": 1.1122627,
+ "bottom_left_y": 1.1122627
+ },
+ {
+ "top_left_x": 0.44889355,
+ "top_left_y": 0.44889355,
+ "top_right_x": 0.44889355,
+ "top_right_y": 0.44889355,
+ "bottom_right_x": 0.8977871,
+ "bottom_right_y": 0.8977871,
+ "bottom_left_x": 0.8977871,
+ "bottom_left_y": 0.8977871
+ },
+ {
+ "top_left_x": 0.34557533,
+ "top_left_y": 0.34557533,
+ "top_right_x": 0.34557533,
+ "top_right_y": 0.34557533,
+ "bottom_right_x": 0.69115067,
+ "bottom_right_y": 0.69115067,
+ "bottom_left_x": 0.69115067,
+ "bottom_left_y": 0.69115067
+ },
+ {
+ "top_left_x": 0.27671337,
+ "top_left_y": 0.27671337,
+ "top_right_x": 0.27671337,
+ "top_right_y": 0.27671337,
+ "bottom_right_x": 0.55342674,
+ "bottom_right_y": 0.55342674,
+ "bottom_left_x": 0.55342674,
+ "bottom_left_y": 0.55342674
+ },
+ {
+ "top_left_x": 0.20785141,
+ "top_left_y": 0.20785141,
+ "top_right_x": 0.20785141,
+ "top_right_y": 0.20785141,
+ "bottom_right_x": 0.41570282,
+ "bottom_right_y": 0.41570282,
+ "bottom_left_x": 0.41570282,
+ "bottom_left_y": 0.41570282
+ },
+ {
+ "top_left_x": 0.1601448,
+ "top_left_y": 0.1601448,
+ "top_right_x": 0.1601448,
+ "top_right_y": 0.1601448,
+ "bottom_right_x": 0.3202896,
+ "bottom_right_y": 0.3202896,
+ "bottom_left_x": 0.3202896,
+ "bottom_left_y": 0.3202896
+ },
+ {
+ "top_left_x": 0.117860794,
+ "top_left_y": 0.117860794,
+ "top_right_x": 0.117860794,
+ "top_right_y": 0.117860794,
+ "bottom_right_x": 0.23572159,
+ "bottom_right_y": 0.23572159,
+ "bottom_left_x": 0.23572159,
+ "bottom_left_y": 0.23572159
+ },
+ {
+ "top_left_x": 0.08036041,
+ "top_left_y": 0.08036041,
+ "top_right_x": 0.08036041,
+ "top_right_y": 0.08036041,
+ "bottom_right_x": 0.16072083,
+ "bottom_right_y": 0.16072083,
+ "bottom_left_x": 0.16072083,
+ "bottom_left_y": 0.16072083
+ },
+ {
+ "top_left_x": 0.05836296,
+ "top_left_y": 0.05836296,
+ "top_right_x": 0.05836296,
+ "top_right_y": 0.05836296,
+ "bottom_right_x": 0.11672592,
+ "bottom_right_y": 0.11672592,
+ "bottom_left_x": 0.11672592,
+ "bottom_left_y": 0.11672592
+ },
+ {
+ "top_left_x": 0.03636551,
+ "top_left_y": 0.03636551,
+ "top_right_x": 0.03636551,
+ "top_right_y": 0.03636551,
+ "bottom_right_x": 0.07273102,
+ "bottom_right_y": 0.07273102,
+ "bottom_left_x": 0.07273102,
+ "bottom_left_y": 0.07273102
+ },
+ {
+ "top_left_x": 0.018137932,
+ "top_left_y": 0.018137932,
+ "top_right_x": 0.018137932,
+ "top_right_y": 0.018137932,
+ "bottom_right_x": 0.036275864,
+ "bottom_right_y": 0.036275864,
+ "bottom_left_x": 0.036275864,
+ "bottom_left_y": 0.036275864
+ },
+ {
+ "top_left_x": 0.0082063675,
+ "top_left_y": 0.0082063675,
+ "top_right_x": 0.0082063675,
+ "top_right_y": 0.0082063675,
+ "bottom_right_x": 0.016412735,
+ "bottom_right_y": 0.016412735,
+ "bottom_left_x": 0.016412735,
+ "bottom_left_y": 0.016412735
+ },
+ {
+ "top_left_x": 0.0031013489,
+ "top_left_y": 0.0031013489,
+ "top_right_x": 0.0031013489,
+ "top_right_y": 0.0031013489,
+ "bottom_right_x": 0.0062026978,
+ "bottom_right_y": 0.0062026978,
+ "bottom_left_x": 0.0062026978,
+ "bottom_left_y": 0.0062026978
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 233,
+ 191,
+ 153,
+ 117,
+ 85,
+ 57,
+ 33,
+ 14,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
new file mode 100644
index 000000000000..848c7d460d3b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
new file mode 100644
index 000000000000..848c7d460d3b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/animations/withoutFade_withoutHole_whenReturning_withSpring_backgroundAnimationTimeSeries_drawHoleAfterFadeout.json
@@ -0,0 +1,375 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 104,
+ "top": 285,
+ "right": 215,
+ "bottom": 414
+ },
+ {
+ "left": 92,
+ "top": 252,
+ "right": 227,
+ "bottom": 447
+ },
+ {
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
+ },
+ {
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
+ },
+ {
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
+ },
+ {
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
+ },
+ {
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
+ },
+ {
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
+ },
+ {
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
+ },
+ {
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
+ },
+ {
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
+ },
+ {
+ "left": 7,
+ "top": 24,
+ "right": 312,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 18,
+ "right": 314,
+ "bottom": 678
+ },
+ {
+ "left": 4,
+ "top": 13,
+ "right": 315,
+ "bottom": 681
+ },
+ {
+ "left": 3,
+ "top": 10,
+ "right": 316,
+ "bottom": 684
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 5,
+ "right": 318,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 4,
+ "right": 318,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 3,
+ "right": 319,
+ "bottom": 688
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 9.492916,
+ "top_left_y": 9.492916,
+ "top_right_x": 9.492916,
+ "top_right_y": 9.492916,
+ "bottom_right_x": 18.985832,
+ "bottom_right_y": 18.985832,
+ "bottom_left_x": 18.985832,
+ "bottom_left_y": 18.985832
+ },
+ {
+ "top_left_x": 8.381761,
+ "top_left_y": 8.381761,
+ "top_right_x": 8.381761,
+ "top_right_y": 8.381761,
+ "bottom_right_x": 16.763521,
+ "bottom_right_y": 16.763521,
+ "bottom_left_x": 16.763521,
+ "bottom_left_y": 16.763521
+ },
+ {
+ "top_left_x": 7.07397,
+ "top_left_y": 7.07397,
+ "top_right_x": 7.07397,
+ "top_right_y": 7.07397,
+ "bottom_right_x": 14.14794,
+ "bottom_right_y": 14.14794,
+ "bottom_left_x": 14.14794,
+ "bottom_left_y": 14.14794
+ },
+ {
+ "top_left_x": 5.7880254,
+ "top_left_y": 5.7880254,
+ "top_right_x": 5.7880254,
+ "top_right_y": 5.7880254,
+ "bottom_right_x": 11.576051,
+ "bottom_right_y": 11.576051,
+ "bottom_left_x": 11.576051,
+ "bottom_left_y": 11.576051
+ },
+ {
+ "top_left_x": 4.6295347,
+ "top_left_y": 4.6295347,
+ "top_right_x": 4.6295347,
+ "top_right_y": 4.6295347,
+ "bottom_right_x": 9.259069,
+ "bottom_right_y": 9.259069,
+ "bottom_left_x": 9.259069,
+ "bottom_left_y": 9.259069
+ },
+ {
+ "top_left_x": 3.638935,
+ "top_left_y": 3.638935,
+ "top_right_x": 3.638935,
+ "top_right_y": 3.638935,
+ "bottom_right_x": 7.27787,
+ "bottom_right_y": 7.27787,
+ "bottom_left_x": 7.27787,
+ "bottom_left_y": 7.27787
+ },
+ {
+ "top_left_x": 2.8209057,
+ "top_left_y": 2.8209057,
+ "top_right_x": 2.8209057,
+ "top_right_y": 2.8209057,
+ "bottom_right_x": 5.6418114,
+ "bottom_right_y": 5.6418114,
+ "bottom_left_x": 5.6418114,
+ "bottom_left_y": 5.6418114
+ },
+ {
+ "top_left_x": 2.1620893,
+ "top_left_y": 2.1620893,
+ "top_right_x": 2.1620893,
+ "top_right_y": 2.1620893,
+ "bottom_right_x": 4.3241787,
+ "bottom_right_y": 4.3241787,
+ "bottom_left_x": 4.3241787,
+ "bottom_left_y": 4.3241787
+ },
+ {
+ "top_left_x": 1.6414614,
+ "top_left_y": 1.6414614,
+ "top_right_x": 1.6414614,
+ "top_right_y": 1.6414614,
+ "bottom_right_x": 3.2829227,
+ "bottom_right_y": 3.2829227,
+ "bottom_left_x": 3.2829227,
+ "bottom_left_y": 3.2829227
+ },
+ {
+ "top_left_x": 1.2361269,
+ "top_left_y": 1.2361269,
+ "top_right_x": 1.2361269,
+ "top_right_y": 1.2361269,
+ "bottom_right_x": 2.4722538,
+ "bottom_right_y": 2.4722538,
+ "bottom_left_x": 2.4722538,
+ "bottom_left_y": 2.4722538
+ },
+ {
+ "top_left_x": 0.92435074,
+ "top_left_y": 0.92435074,
+ "top_right_x": 0.92435074,
+ "top_right_y": 0.92435074,
+ "bottom_right_x": 1.8487015,
+ "bottom_right_y": 1.8487015,
+ "bottom_left_x": 1.8487015,
+ "bottom_left_y": 1.8487015
+ },
+ {
+ "top_left_x": 0.68693924,
+ "top_left_y": 0.68693924,
+ "top_right_x": 0.68693924,
+ "top_right_y": 0.68693924,
+ "bottom_right_x": 1.3738785,
+ "bottom_right_y": 1.3738785,
+ "bottom_left_x": 1.3738785,
+ "bottom_left_y": 1.3738785
+ },
+ {
+ "top_left_x": 0.5076904,
+ "top_left_y": 0.5076904,
+ "top_right_x": 0.5076904,
+ "top_right_y": 0.5076904,
+ "bottom_right_x": 1.0153809,
+ "bottom_right_y": 1.0153809,
+ "bottom_left_x": 1.0153809,
+ "bottom_left_y": 1.0153809
+ },
+ {
+ "top_left_x": 0.3733511,
+ "top_left_y": 0.3733511,
+ "top_right_x": 0.3733511,
+ "top_right_y": 0.3733511,
+ "bottom_right_x": 0.7467022,
+ "bottom_right_y": 0.7467022,
+ "bottom_left_x": 0.7467022,
+ "bottom_left_y": 0.7467022
+ },
+ {
+ "top_left_x": 0.27331638,
+ "top_left_y": 0.27331638,
+ "top_right_x": 0.27331638,
+ "top_right_y": 0.27331638,
+ "bottom_right_x": 0.54663277,
+ "bottom_right_y": 0.54663277,
+ "bottom_left_x": 0.54663277,
+ "bottom_left_y": 0.54663277
+ },
+ {
+ "top_left_x": 0.19925308,
+ "top_left_y": 0.19925308,
+ "top_right_x": 0.19925308,
+ "top_right_y": 0.19925308,
+ "bottom_right_x": 0.39850616,
+ "bottom_right_y": 0.39850616,
+ "bottom_left_x": 0.39850616,
+ "bottom_left_y": 0.39850616
+ },
+ {
+ "top_left_x": 0.14470005,
+ "top_left_y": 0.14470005,
+ "top_right_x": 0.14470005,
+ "top_right_y": 0.14470005,
+ "bottom_right_x": 0.2894001,
+ "bottom_right_y": 0.2894001,
+ "bottom_left_x": 0.2894001,
+ "bottom_left_y": 0.2894001
+ },
+ {
+ "top_left_x": 0.10470486,
+ "top_left_y": 0.10470486,
+ "top_right_x": 0.10470486,
+ "top_right_y": 0.10470486,
+ "bottom_right_x": 0.20940971,
+ "bottom_right_y": 0.20940971,
+ "bottom_left_x": 0.20940971,
+ "bottom_left_y": 0.20940971
+ },
+ {
+ "top_left_x": 0.07550812,
+ "top_left_y": 0.07550812,
+ "top_right_x": 0.07550812,
+ "top_right_y": 0.07550812,
+ "bottom_right_x": 0.15101624,
+ "bottom_right_y": 0.15101624,
+ "bottom_left_x": 0.15101624,
+ "bottom_left_y": 0.15101624
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 249,
+ 226,
+ 192,
+ 153,
+ 112,
+ 72,
+ 34,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a42f5d3f67ea..9475bdbd043b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -50,7 +50,6 @@ import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -1878,7 +1877,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verify(callback, never()).onTrustGrantedForCurrentUser(
anyBoolean() /* dismissKeyguard */,
eq(true) /* newlyUnlocked */,
- anyObject() /* flags */,
+ any() /* flags */,
anyString() /* message */
);
}
@@ -2747,7 +2746,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
() -> mJavaAdapter,
() -> mSceneInteractor,
() -> mCommunalSceneInteractor,
- mKeyguardServiceShowLockscreenInteractor);
+ () -> mKeyguardServiceShowLockscreenInteractor);
setAlternateBouncerVisibility(false);
setPrimaryBouncerVisibility(false);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
index 4f7610ab7d72..f98c1309b24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.GlobalRootComponent
import com.android.systemui.dagger.SysUIComponent
import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.systemPropertiesHelper
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.process.processWrapper
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -48,7 +47,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
@get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
- val kosmos = Kosmos()
+ val kosmos = testKosmos()
@Mock private lateinit var initializer: SystemUIInitializer
@Mock private lateinit var rootComponent: GlobalRootComponent
@Mock private lateinit var sysuiComponent: SysUIComponent
@@ -56,9 +55,13 @@ class SystemUIApplicationTest : SysuiTestCase() {
@Mock private lateinit var initController: InitController
class StartableA : TestableStartable()
+
class StartableB : TestableStartable()
+
class StartableC : TestableStartable()
+
class StartableD : TestableStartable()
+
class StartableE : TestableStartable()
val dependencyMap: Map<Class<*>, Set<Class<out CoreStartable>>> =
@@ -114,7 +117,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
.thenReturn(
mutableMapOf(
StartableA::class.java to Provider { startableA },
- StartableB::class.java to Provider { startableB }
+ StartableB::class.java to Provider { startableB },
)
)
app.onCreate()
@@ -130,7 +133,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
mutableMapOf(
StartableC::class.java to Provider { startableC },
StartableA::class.java to Provider { startableA },
- StartableB::class.java to Provider { startableB }
+ StartableB::class.java to Provider { startableB },
)
)
app.onCreate()
@@ -150,7 +153,7 @@ class SystemUIApplicationTest : SysuiTestCase() {
StartableC::class.java to Provider { startableC },
StartableD::class.java to Provider { startableD },
StartableA::class.java to Provider { startableA },
- StartableB::class.java to Provider { startableB }
+ StartableB::class.java to Provider { startableB },
)
)
app.onCreate()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 7cf93277bb5b..5b32b922d377 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -191,7 +191,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
+ mSysUiState = mKosmos.getSysuiState();
mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
returnsSecondArg());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 845be0252581..60345a358bac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -24,12 +24,15 @@ import android.widget.LinearLayout
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.WindowAnimationState
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shared.Flags
+import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.wm.shell.shared.ShellTransitions
@@ -43,7 +46,6 @@ import kotlin.concurrent.thread
import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -57,8 +59,8 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -66,21 +68,23 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val transitionContainer = LinearLayout(mContext)
+
private val mainExecutor = context.mainExecutor
private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
private val testShellTransitions = FakeShellTransitions()
+
+ private val Kosmos.underTest by Kosmos.Fixture { activityTransitionAnimator }
+
@Mock lateinit var callback: ActivityTransitionAnimator.Callback
@Mock lateinit var listener: ActivityTransitionAnimator.Listener
- @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
- private lateinit var underTest: ActivityTransitionAnimator
- @get:Rule val rule = MockitoJUnit.rule()
+ @get:Rule(order = 0) val mockitoRule = MockitoJUnit.rule()
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
@Before
fun setup() {
- underTest =
+ kosmos.activityTransitionAnimator =
ActivityTransitionAnimator(
mainExecutor,
ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
@@ -89,19 +93,20 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
testTransitionAnimator,
testTransitionAnimator,
disableWmTimeout = true,
+ skipReparentTransaction = true,
)
- underTest.callback = callback
- underTest.addListener(listener)
+ kosmos.activityTransitionAnimator.callback = callback
+ kosmos.activityTransitionAnimator.addListener(listener)
}
@After
fun tearDown() {
- underTest.removeListener(listener)
+ kosmos.activityTransitionAnimator.removeListener(listener)
}
private fun startIntentWithAnimation(
- animator: ActivityTransitionAnimator = underTest,
- controller: ActivityTransitionAnimator.Controller? = this.controller,
+ controller: ActivityTransitionAnimator.Controller?,
+ animator: ActivityTransitionAnimator = kosmos.activityTransitionAnimator,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int,
) {
@@ -119,129 +124,152 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun animationAdapterIsNullIfControllerIsNull() {
- var startedIntent = false
- var animationAdapter: RemoteAnimationAdapter? = null
+ kosmos.runTest {
+ var startedIntent = false
+ var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(controller = null) { adapter ->
- startedIntent = true
- animationAdapter = adapter
+ startIntentWithAnimation(controller = null) { adapter ->
+ startedIntent = true
+ animationAdapter = adapter
- ActivityManager.START_SUCCESS
- }
+ ActivityManager.START_SUCCESS
+ }
- assertTrue(startedIntent)
- assertNull(animationAdapter)
+ assertTrue(startedIntent)
+ assertNull(animationAdapter)
+ }
}
@Test
fun animatesIfActivityOpens() {
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation { adapter ->
- animationAdapter = adapter
- ActivityManager.START_SUCCESS
- }
+ kosmos.runTest {
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ var animationAdapter: RemoteAnimationAdapter? = null
+ startIntentWithAnimation(controller) { adapter ->
+ animationAdapter = adapter
+ ActivityManager.START_SUCCESS
+ }
- assertNotNull(animationAdapter)
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- assertTrue(willAnimateCaptor.value)
+ assertNotNull(animationAdapter)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertTrue(willAnimateCaptor.value)
+ }
}
@Test
fun doesNotAnimateIfActivityIsAlreadyOpen() {
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- startIntentWithAnimation { ActivityManager.START_DELIVERED_TO_TOP }
+ kosmos.runTest {
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ startIntentWithAnimation(controller) { ActivityManager.START_DELIVERED_TO_TOP }
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- assertFalse(willAnimateCaptor.value)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertFalse(willAnimateCaptor.value)
+ }
}
@Test
fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
- `when`(callback.isOnKeyguard()).thenReturn(true)
+ kosmos.runTest {
+ `when`(callback.isOnKeyguard()).thenReturn(true)
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- var animationAdapter: RemoteAnimationAdapter? = null
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(underTest) { adapter ->
- animationAdapter = adapter
- ActivityManager.START_DELIVERED_TO_TOP
- }
+ startIntentWithAnimation(controller, underTest) { adapter ->
+ animationAdapter = adapter
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- verify(callback).hideKeyguardWithAnimation(any())
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ verify(callback).hideKeyguardWithAnimation(any())
- assertTrue(willAnimateCaptor.value)
- assertNull(animationAdapter)
+ assertTrue(willAnimateCaptor.value)
+ assertNull(animationAdapter)
+ }
}
@Test
fun doesNotAnimateIfAnimateIsFalse() {
- val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- startIntentWithAnimation(animate = false) { ActivityManager.START_SUCCESS }
+ kosmos.runTest {
+ val controller = createController()
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ startIntentWithAnimation(controller, animate = false) { ActivityManager.START_SUCCESS }
- waitForIdleSync()
- verify(controller).onIntentStarted(willAnimateCaptor.capture())
- assertFalse(willAnimateCaptor.value)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertFalse(willAnimateCaptor.value)
+ }
}
- @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
+ @EnableFlags(SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
@Test
fun registersReturnIffCookieIsPresent() {
- `when`(callback.isOnKeyguard()).thenReturn(false)
+ kosmos.runTest {
+ `when`(callback.isOnKeyguard()).thenReturn(false)
- startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
+ val controller = createController()
+ startIntentWithAnimation(controller, underTest) {
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
- waitForIdleSync()
- assertTrue(testShellTransitions.remotes.isEmpty())
- assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ waitForIdleSync()
+ assertTrue(testShellTransitions.remotes.isEmpty())
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
- val controller =
- object : DelegateTransitionAnimatorController(controller) {
- override val transitionCookie
- get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
- }
+ val controllerWithCookie =
+ object : DelegateTransitionAnimatorController(controller) {
+ override val transitionCookie
+ get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
+ }
- startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
+ startIntentWithAnimation(controllerWithCookie, underTest) {
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
- waitForIdleSync()
- assertEquals(1, testShellTransitions.remotes.size)
- assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ waitForIdleSync()
+ assertEquals(1, testShellTransitions.remotes.size)
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ }
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun registersLongLivedTransition() {
kosmos.runTest {
- var factory = controllerFactory()
+ val controller = createController()
+ var factory = controllerFactory(controller)
underTest.register(factory.cookie, factory, testScope)
assertEquals(2, testShellTransitions.remotes.size)
- factory = controllerFactory()
+ factory = controllerFactory(controller)
underTest.register(factory.cookie, factory, testScope)
assertEquals(4, testShellTransitions.remotes.size)
}
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun registersLongLivedTransitionOverridingPreviousRegistration() {
kosmos.runTest {
+ val controller = createController()
val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
- var factory = controllerFactory(cookie)
+ var factory = controllerFactory(controller, cookie)
underTest.register(cookie, factory, testScope)
val transitions = testShellTransitions.remotes.values.toList()
- factory = controllerFactory(cookie)
+ factory = controllerFactory(controller, cookie)
underTest.register(cookie, factory, testScope)
assertEquals(2, testShellTransitions.remotes.size)
for (transition in transitions) {
@@ -250,23 +278,25 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
}
- @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+ @DisableFlags(SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
kosmos.runTest {
- val factory = controllerFactory(component = null)
+ val factory = controllerFactory(createController(), component = null)
assertThrows(IllegalStateException::class.java) {
underTest.register(factory.cookie, factory, testScope)
}
}
}
- @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+ @EnableFlags(SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
kosmos.runTest {
+ val controller = createController()
+
// No ComponentName
- var factory = controllerFactory(component = null)
+ var factory = controllerFactory(controller, component = null)
assertThrows(IllegalStateException::class.java) {
underTest.register(factory.cookie, factory, testScope)
}
@@ -280,7 +310,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
testTransitionAnimator,
disableWmTimeout = true,
)
- factory = controllerFactory()
+ factory = controllerFactory(controller)
assertThrows(IllegalStateException::class.java) {
activityTransitionAnimator.register(factory.cookie, factory, testScope)
}
@@ -288,17 +318,18 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun unregistersLongLivedTransition() {
kosmos.runTest {
+ val controller = createController()
val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
for (index in 0 until 3) {
cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val factory = controllerFactory(cookies[index]!!)
+ val factory = controllerFactory(controller, cookies[index]!!)
underTest.register(factory.cookie, factory, testScope)
}
@@ -315,75 +346,98 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun doesNotStartIfAnimationIsCancelled() {
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationCancelled()
- runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationCancelled()
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ emptyArray(),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
- waitForIdleSync()
- verify(controller).onTransitionAnimationCancelled()
- verify(controller, never()).onTransitionAnimationStart(anyBoolean())
- verify(listener).onTransitionAnimationCancelled()
- verify(listener, never()).onTransitionAnimationStart()
- assertNull(runner.delegate)
+ waitForIdleSync()
+ verify(controller).onTransitionAnimationCancelled()
+ verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationCancelled()
+ verify(listener, never()).onTransitionAnimationStart()
+ assertNull(runner.delegate)
+ }
}
@Test
fun cancelsIfNoOpeningWindowIsFound() {
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ emptyArray(),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
- waitForIdleSync()
- verify(controller).onTransitionAnimationCancelled()
- verify(controller, never()).onTransitionAnimationStart(anyBoolean())
- verify(listener).onTransitionAnimationCancelled()
- verify(listener, never()).onTransitionAnimationStart()
- assertNull(runner.delegate)
+ waitForIdleSync()
+ verify(controller).onTransitionAnimationCancelled()
+ verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+ verify(listener).onTransitionAnimationCancelled()
+ verify(listener, never()).onTransitionAnimationStart()
+ assertNull(runner.delegate)
+ }
}
@Test
fun startsAnimationIfWindowIsOpening() {
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationStart(
- TRANSIT_NONE,
- arrayOf(fakeWindow()),
- emptyArray(),
- emptyArray(),
- iCallback,
- )
- waitForIdleSync()
- verify(listener).onTransitionAnimationStart()
- verify(controller).onTransitionAnimationStart(anyBoolean())
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+ waitForIdleSync()
+ verify(listener).onTransitionAnimationStart()
+ verify(controller).onTransitionAnimationStart(anyBoolean())
+ }
}
@Test
fun creatingControllerFromNormalViewThrows() {
- assertThrows(IllegalArgumentException::class.java) {
- ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext))
+ kosmos.runTest {
+ assertThrows(IllegalArgumentException::class.java) {
+ ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext))
+ }
}
}
@DisableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
kosmos.runTest {
assertThrows(IllegalStateException::class.java) {
- val factory = controllerFactory()
+ val factory = controllerFactory(createController())
underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
}
}
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun runnerCreatesDelegateLazily_onAnimationStart() {
kosmos.runTest {
- val factory = controllerFactory()
+ val factory = controllerFactory(createController())
val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
assertNull(runner.delegate)
@@ -412,13 +466,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun runnerCreatesDelegateLazily_onAnimationTakeover() {
kosmos.runTest {
- val factory = controllerFactory()
+ val factory = controllerFactory(createController())
val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = false)
assertNull(runner.delegate)
@@ -446,58 +500,78 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@DisableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
- val runner = underTest.createEphemeralRunner(controller)
- assertThrows(IllegalStateException::class.java) {
- runner.takeOverAnimation(
- arrayOf(fakeWindow()),
- emptyArray(),
- SurfaceControl.Transaction(),
- iCallback,
- )
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ assertThrows(IllegalStateException::class.java) {
+ runner.takeOverAnimation(
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ SurfaceControl.Transaction(),
+ iCallback,
+ )
+ }
}
}
@DisableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
fun disposeRunner_delegateDereferenced() {
- val runner = underTest.createEphemeralRunner(controller)
- assertNotNull(runner.delegate)
- runner.dispose()
- waitForIdleSync()
- assertNull(runner.delegate)
+ kosmos.runTest {
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ assertNotNull(runner.delegate)
+ runner.dispose()
+ waitForIdleSync()
+ assertNull(runner.delegate)
+ }
}
@Test
fun concurrentListenerModification_doesNotThrow() {
- // Need a second listener to trigger the concurrent modification.
- underTest.addListener(object : ActivityTransitionAnimator.Listener {})
- `when`(listener.onTransitionAnimationStart()).thenAnswer {
- underTest.removeListener(listener)
- listener
- }
+ kosmos.runTest {
+ // Need a second listener to trigger the concurrent modification.
+ underTest.addListener(object : ActivityTransitionAnimator.Listener {})
+ `when`(listener.onTransitionAnimationStart()).thenAnswer {
+ underTest.removeListener(listener)
+ listener
+ }
- val runner = underTest.createEphemeralRunner(controller)
- runner.onAnimationStart(
- TRANSIT_NONE,
- arrayOf(fakeWindow()),
- emptyArray(),
- emptyArray(),
- iCallback,
- )
+ val controller = createController()
+ val runner = underTest.createEphemeralRunner(controller)
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+
+ waitForIdleSync()
+ verify(listener).onTransitionAnimationStart()
+ }
+ }
+ private fun createController(): TestTransitionAnimatorController {
+ lateinit var transitionContainer: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ transitionContainer = LinearLayout(activity)
+ activity.setContentView(transitionContainer)
+ }
waitForIdleSync()
- verify(listener).onTransitionAnimationStart()
+ return spy(TestTransitionAnimatorController(transitionContainer))
}
private fun controllerFactory(
+ controller: ActivityTransitionAnimator.Controller,
cookie: ActivityTransitionAnimator.TransitionCookie =
mock(ActivityTransitionAnimator.TransitionCookie::class.java),
component: ComponentName? = mock(ComponentName::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index a1f59c2cc2b5..4cfe106780f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -23,17 +23,20 @@ import android.animation.recordMotion
import android.graphics.Color
import android.graphics.PointF
import android.graphics.drawable.GradientDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.MotionTest
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.concurrency.fakeExecutor
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.testKosmos
import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -45,33 +48,74 @@ import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
+import platform.test.screenshot.PathElementNoContext
@SmallTest
@MotionTest
@RunWith(ParameterizedAndroidJunit4::class)
class TransitionAnimatorTest(
private val fadeWindowBackgroundLayer: Boolean,
+ private val drawHole: Boolean,
private val isLaunching: Boolean,
private val useSpring: Boolean,
) : SysuiTestCase() {
companion object {
private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens"
- @get:Parameters(name = "fadeBackground={0}, isLaunching={1}, useSpring={2}")
+ @get:Parameters(name = "fadeBackground={0}, drawHole={1}, isLaunching={2}, useSpring={3}")
@JvmStatic
val parameterValues = buildList {
booleanArrayOf(true, false).forEach { fadeBackground ->
- booleanArrayOf(true, false).forEach { isLaunching ->
- booleanArrayOf(true, false).forEach { useSpring ->
- add(arrayOf(fadeBackground, isLaunching, useSpring))
+ booleanArrayOf(true, false).forEach { drawHole ->
+ booleanArrayOf(true, false).forEach { isLaunching ->
+ booleanArrayOf(true, false).forEach { useSpring ->
+ add(arrayOf(fadeBackground, drawHole, isLaunching, useSpring))
+ }
}
}
}
}
}
- private val kosmos = Kosmos()
- private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
+ private val kosmos = testKosmos()
+ private val pathManager =
+ GoldenPathManager(
+ context,
+ GOLDENS_PATH,
+ pathConfig =
+ PathConfig(
+ PathElementNoContext("base", isDir = true) { "animations" },
+ PathElementNoContext("fade", isDir = false) {
+ if (fadeWindowBackgroundLayer) {
+ "withFade"
+ } else {
+ "withoutFade"
+ }
+ },
+ PathElementNoContext("hole", isDir = false) {
+ if (drawHole) {
+ "withHole"
+ } else {
+ "withoutHole"
+ }
+ },
+ PathElementNoContext("direction", isDir = false) {
+ if (isLaunching) {
+ "whenLaunching"
+ } else {
+ "whenReturning"
+ }
+ },
+ PathElementNoContext("mode", isDir = false) {
+ if (useSpring) {
+ "withSpring"
+ } else {
+ "withAnimator"
+ }
+ },
+ ),
+ )
+
private val transitionAnimator =
TransitionAnimator(
kosmos.fakeExecutor,
@@ -80,24 +124,6 @@ class TransitionAnimatorTest(
ActivityTransitionAnimator.SPRING_TIMINGS,
ActivityTransitionAnimator.SPRING_INTERPOLATORS,
)
- private val fade =
- if (fadeWindowBackgroundLayer) {
- "withFade"
- } else {
- "withoutFade"
- }
- private val direction =
- if (isLaunching) {
- "whenLaunching"
- } else {
- "whenReturning"
- }
- private val mode =
- if (useSpring) {
- "withSpring"
- } else {
- "withAnimator"
- }
@get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
@get:Rule(order = 2) val animatorTestRule = android.animation.AnimatorTestRule(this)
@@ -108,6 +134,7 @@ class TransitionAnimatorTest(
pathManager,
)
+ @DisableFlags(Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER)
@Test
fun backgroundAnimationTimeSeries() {
val transitionContainer = createScene()
@@ -118,7 +145,21 @@ class TransitionAnimatorTest(
motionRule
.assertThat(recordedMotion)
- .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_${fade}_${direction}_$mode")
+ .timeSeriesMatchesGolden("backgroundAnimationTimeSeries")
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_TRANSITION_ANIMATION_LAYER)
+ @Test
+ fun backgroundAnimationTimeSeries_drawHoleAfterFadeout() {
+ val transitionContainer = createScene()
+ val backgroundLayer = createBackgroundLayer()
+ val animation = createAnimation(transitionContainer, backgroundLayer)
+
+ val recordedMotion = record(backgroundLayer, animation)
+
+ motionRule
+ .assertThat(recordedMotion)
+ .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_drawHoleAfterFadeout")
}
private fun createScene(): ViewGroup {
@@ -169,6 +210,7 @@ class TransitionAnimatorTest(
endState,
backgroundLayer,
fadeWindowBackgroundLayer,
+ drawHole,
startVelocity = startVelocity,
)
.apply { runOnMainThreadAndWaitForIdleSync { start() } }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
index 75a5768193cf..c81900da2581 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
@@ -69,9 +69,11 @@ class FlingOnBackAnimationCallbackTest : SysuiTestCase() {
callback.onBackProgressed(backEventOf(0.6f, 32))
assertTrue("Assert onBackProgressedCompat called", callback.backProgressedCalled)
assertEquals("Assert interpolated progress", 0.6f, callback.progressEvent?.progress)
- getInstrumentation().runOnMainSync { callback.onBackInvoked() }
- // Assert that onBackInvoked is not called immediately...
- assertFalse(callback.backInvokedCalled)
+ getInstrumentation().runOnMainSync {
+ callback.onBackInvoked()
+ // Assert that onBackInvoked is not called immediately.
+ assertFalse(callback.backInvokedCalled)
+ }
// Instead the fling animation is played and eventually onBackInvoked is called.
callback.backInvokedLatch.await(1000, TimeUnit.MILLISECONDS)
assertTrue(callback.backInvokedCalled)
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 5249620dbdd0..a1d038ad8554 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -120,9 +120,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
private lateinit var deviceEntryUdfpsTouchOverlayViewModel:
DeviceEntryUdfpsTouchOverlayViewModel
@Mock private lateinit var defaultUdfpsTouchOverlayViewModel: DefaultUdfpsTouchOverlayViewModel
- @Mock
- private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ @Mock private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
@@ -185,7 +183,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
primaryBouncerInteractor,
alternateBouncerInteractor,
isDebuggable,
- udfpsKeyguardAccessibilityDelegate,
keyguardTransitionInteractor,
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 0e68fce679b0..2c70249bcb06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -73,9 +73,9 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
@@ -189,7 +189,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
private lateinit var promptContentViewWithMoreOptionsButton:
PromptContentViewWithMoreOptionsButton
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 7c8822bc11ec..96ef8b690145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -18,8 +18,6 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.graphics.drawable.Drawable
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import android.util.Pair
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -29,7 +27,6 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
@@ -208,42 +205,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_bondedAndNotConnected_returnsTrue() {
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(cachedDevice.isConnected).thenReturn(false)
-
- assertThat(
- savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
- )
- .isTrue()
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_connected_returnsFalse() {
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
- `when`(cachedDevice.isConnected).thenReturn(true)
-
- assertThat(
- savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
- )
- .isFalse()
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
- `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
-
- assertThat(
- savedDeviceItemFactory.isFilterMatched(context, cachedDevice, isOngoingCall = false)
- )
- .isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
@@ -255,7 +216,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_notExclusiveManaged_returnsTrue() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
@@ -269,7 +229,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_notExclusivelyManaged_connected_returnsFalse() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
@@ -283,35 +242,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
- `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
-
- assertThat(
- connectedDeviceItemFactory.isFilterMatched(
- context,
- cachedDevice,
- isOngoingCall = false,
- )
- )
- .isTrue()
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
- assertThat(
- connectedDeviceItemFactory.isFilterMatched(
- context,
- cachedDevice,
- isOngoingCall = false,
- )
- )
- .isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
@@ -327,7 +257,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
@@ -344,7 +273,6 @@ class DeviceItemFactoryTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notConnected_returnsFalse() {
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
index 9dab9d735603..b134dff89651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
@@ -31,8 +31,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.motion.createSysUiComposeMotionTestRule
+import com.android.systemui.testKosmos
import com.android.systemui.utils.PolicyRestriction
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
@@ -61,7 +61,7 @@ import platform.test.screenshot.Displays.Phone
class BrightnessSliderMotionTest : SysuiTestCase() {
private val deviceSpec = DeviceEmulationSpec(Phone)
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
@get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 562481567536..6a4f3da054e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -28,7 +28,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
-import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -45,6 +44,7 @@ import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
@@ -60,7 +60,6 @@ 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.flags.FakeFeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -79,6 +78,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Optional;
+import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -102,7 +102,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Mock
private UiEventLogger mUiEventLogger;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private Animator mAnimator;
@@ -152,8 +151,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
-
- mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
}
/**
@@ -163,6 +160,31 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
* is false are removed.[
*/
private void initController() {
+ IntentCreator fakeIntentCreator = new IntentCreator() {
+ @Override
+ public Intent getTextEditorIntent(Context context) {
+ return new Intent();
+ }
+
+ @Override
+ public Intent getShareIntent(ClipData clipData, Context context) {
+ Intent intent = Intent.createChooser(new Intent(Intent.ACTION_SEND), null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ @Override
+ public void getImageEditIntentAsync(Uri uri, Context context,
+ Consumer<Intent> outputConsumer) {
+ outputConsumer.accept(new Intent(Intent.ACTION_EDIT));
+ }
+
+ @Override
+ public Intent getRemoteCopyIntent(ClipData clipData, Context context) {
+ return new Intent();
+ }
+ };
+
mOverlayController = new ClipboardOverlayController(
mContext,
mClipboardOverlayView,
@@ -170,13 +192,13 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
getFakeBroadcastDispatcher(),
mBroadcastSender,
mTimeoutHandler,
- mFeatureFlags,
mClipboardUtils,
mExecutor,
mClipboardImageLoader,
mClipboardTransitionExecutor,
mClipboardIndicationProvider,
- mUiEventLogger);
+ mUiEventLogger,
+ fakeIntentCreator);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
@@ -193,7 +215,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
ClipData clipData = new ClipData("", new String[]{"image/png"},
new ClipData.Item(Uri.parse("")));
- mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
mOverlayController.setClipData(clipData, "");
@@ -208,7 +229,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
ClipData clipData = new ClipData("", new String[]{"resource/png"},
new ClipData.Item(Uri.parse("")));
- mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
mOverlayController.setClipData(clipData, "");
@@ -219,7 +239,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_setClipData_textData_legacy() {
- mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
initController();
mOverlayController.setClipData(mSampleClipData, "abc");
@@ -232,7 +251,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_setClipData_sensitiveTextData_legacy() {
- mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
initController();
ClipDescription description = mSampleClipData.getDescription();
@@ -250,7 +268,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_setClipData_repeatedCalls_legacy() {
when(mAnimator.isRunning()).thenReturn(true);
- mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
initController();
mOverlayController.setClipData(mSampleClipData, "");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index fb70846049da..061f7984d44b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -134,7 +134,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
@Mock private PackageManager mPackageManager;
- @Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@Mock private VibratorHelper mVibratorHelper;
@Mock private ShadeController mShadeController;
@@ -148,6 +147,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
private TestableLooper mTestableLooper;
private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private GlobalActionsInteractor mInteractor;
+ private Handler mHandler;
@Before
public void setUp() throws Exception {
@@ -166,6 +166,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalSettings = new FakeGlobalSettings();
mSecureSettings = new FakeSettings();
mInteractor = mKosmos.getGlobalActionsInteractor();
+ mHandler = new Handler(mTestableLooper.getLooper());
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
@@ -771,6 +772,31 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY);
}
+ @Test
+ public void userSwitching_dismissDialog() {
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mResources)
+ .getStringArray(com.android.internal.R.array.config_globalActionsList);
+
+ mGlobalActionsDialogLite.showOrHideDialog(false, true, null, Display.DEFAULT_DISPLAY);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mGlobalActionsDialogLite.mDialog.isShowing()).isTrue();
+
+ ArgumentCaptor<UserTracker.Callback> captor =
+ ArgumentCaptor.forClass(UserTracker.Callback.class);
+
+ verify(mUserTracker).addCallback(captor.capture(), any());
+
+ captor.getValue().onBeforeUserSwitching(100);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mGlobalActionsDialogLite.mDialog).isNull();
+ }
+
private UserInfo mockCurrentUser(int flags) {
return new UserInfo(10, "A User", flags);
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 e8054c07eac8..8105ae0960ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -283,7 +283,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mock(UserTracker.class),
mKosmos.getNotificationShadeWindowModel(),
- mSecureSettings,
mKosmos::getCommunalInteractor,
mKosmos.getShadeLayoutParams());
mFeatureFlags = new FakeFeatureFlags();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index b274f1c2c6df..d9c59d37b031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -88,11 +88,6 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
companion object {
private val INTENT = Intent("some.intent.action")
- private val DRAWABLE =
- mock<Icon> {
- whenever(this.contentDescription)
- .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- }
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
@Parameters(
@@ -337,7 +332,17 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
homeControls.setState(
lockScreenState =
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE)
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ mock<Icon> {
+ whenever(contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID
+ )
+ )
+ }
+ )
)
homeControls.onTriggeredResult =
if (startActivity) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 175e8d4331a1..e048d462ef8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -33,8 +33,8 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -77,6 +77,8 @@ import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.eq
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
@@ -89,12 +91,24 @@ private const val BROADCAST_APP_NAME = "BROADCAST_APP_NAME"
private const val NORMAL_APP_NAME = "NORMAL_APP_NAME"
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-public class MediaDeviceManagerTest : SysuiTestCase() {
+public class MediaDeviceManagerTest(flags: FlagsParameterization) : SysuiTestCase() {
- private companion object {
+ companion object {
val OTHER_DEVICE_ICON_STUB = TestStubDrawable()
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@@ -187,6 +201,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
fun loadMediaData() {
manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
verify(lmmFactory).create(PACKAGE)
}
@@ -195,6 +211,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
manager.onMediaDataLoaded(KEY, null, mediaData)
manager.onMediaDataRemoved(KEY, false)
fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
verify(lmm).unregisterCallback(any())
verify(muteAwaitManager).stopListening()
}
@@ -406,6 +423,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// WHEN the notification is removed
manager.onMediaDataRemoved(KEY, true)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
// THEN the listener receives key removed event
verify(listener).onKeyRemoved(eq(KEY), eq(true))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 2001a3ea7ca0..dad08e014c0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -422,25 +422,6 @@ class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase(
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
- @DisableSceneContainer
- @Test
- fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
- verify(mediaDataManager).addListener(capture(listener))
-
- 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()
@@ -571,146 +552,6 @@ class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase(
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
- @DisableSceneContainer
- @Test
- fun testMediaLoaded_ScrollToActivePlayer() {
- verify(mediaDataManager).addListener(capture(listener))
-
- 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,
- ),
- )
- runAllReady()
- // 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,
- ),
- )
- runAllReady()
-
- assertEquals(
- MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
- )
- }
-
- @DisableSceneContainer
- @Test
- fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
- verify(mediaDataManager).addListener(capture(listener))
-
- 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,
- ),
- )
- runAllReady()
-
- 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",
- ),
- )
- runAllReady()
- playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
- assertEquals(playerIndex, 0)
- }
-
- @DisableSceneContainer
- @Test
- fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
- verify(mediaDataManager).addListener(capture(listener))
-
- var result = false
- mediaCarouselController.updateHostVisibility = { result = true }
-
- whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
- listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
-
- assertEquals(true, result)
- }
-
- @DisableSceneContainer
- @Test
- fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
- verify(mediaDataManager).addListener(capture(listener))
-
- 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)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 9543032ef5ec..88fcc706f072 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -19,16 +19,12 @@ package com.android.systemui.media.controls.ui.controller
import android.animation.Animator
import android.animation.AnimatorSet
import android.app.PendingIntent
-import android.app.smartspace.SmartspaceAction
-import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
-import android.graphics.Matrix
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
@@ -47,7 +43,6 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.TestableLooper
-import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
@@ -59,7 +54,6 @@ import android.widget.TextView
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
-import androidx.media.utils.MediaConstants
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -72,19 +66,15 @@ import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import com.android.systemui.media.controls.shared.model.KEY_SMARTSPACE_APP_NAME
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.shared.model.MediaNotificationAction
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.ui.binder.SeekBarObserver
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaViewHolder
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogManager
@@ -216,32 +206,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor
- @Mock private lateinit var recommendationViewHolder: RecommendationViewHolder
- @Mock private lateinit var smartspaceAction: SmartspaceAction
- private lateinit var smartspaceData: SmartspaceMediaData
- @Mock private lateinit var coverContainer1: ViewGroup
- @Mock private lateinit var coverContainer2: ViewGroup
- @Mock private lateinit var coverContainer3: ViewGroup
- @Mock private lateinit var recAppIconItem: CachingIconView
- @Mock private lateinit var recCardTitle: TextView
- @Mock private lateinit var coverItem: ImageView
- @Mock private lateinit var matrix: Matrix
- private lateinit var recTitle1: TextView
- private lateinit var recTitle2: TextView
- private lateinit var recTitle3: TextView
- private lateinit var recSubtitle1: TextView
- private lateinit var recSubtitle2: TextView
- private lateinit var recSubtitle3: TextView
- @Mock private lateinit var recProgressBar1: SeekBar
- @Mock private lateinit var recProgressBar2: SeekBar
- @Mock private lateinit var recProgressBar3: SeekBar
@Mock private lateinit var globalSettings: GlobalSettings
- private val intent =
- Intent().apply {
- putExtras(Bundle().also { it.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) })
- setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
+ private val intent = Intent().apply { setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
private val pendingIntent =
PendingIntent.getActivity(
mContext,
@@ -282,7 +249,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
mediaOutputDialogManager,
mediaCarouselController,
falsingManager,
- clock,
logger,
keyguardStateController,
activityIntentHelper,
@@ -304,27 +270,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
initMediaViewHolderMocks()
initDeviceMediaData(false, DEVICE_NAME)
-
- // Set up recommendation view
- initRecommendationViewHolderMocks()
-
- // 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)
- }
- 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,
- )
}
private fun initGutsViewHolderMocks() {
@@ -471,49 +416,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView)
}
- /** Initialize elements for the recommendation view holder */
- private fun initRecommendationViewHolderMocks() {
- recTitle1 = TextView(context)
- recTitle2 = TextView(context)
- recTitle3 = TextView(context)
- recSubtitle1 = TextView(context)
- recSubtitle2 = TextView(context)
- recSubtitle3 = TextView(context)
-
- whenever(recommendationViewHolder.recommendations).thenReturn(view)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaCoverContainers)
- .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
- whenever(recommendationViewHolder.mediaTitles)
- .thenReturn(listOf(recTitle1, recTitle2, recTitle3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- // set ids for recommendation containers
- whenever(coverContainer1.id).thenReturn(1)
- whenever(coverContainer2.id).thenReturn(2)
- whenever(coverContainer3.id).thenReturn(3)
-
- whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
-
- val actionIcon = Icon.createWithResource(context, R.drawable.ic_android)
- whenever(smartspaceAction.icon).thenReturn(actionIcon)
-
- // Needed for card and item action click
- val mockContext = mock(Context::class.java)
- whenever(view.context).thenReturn(mockContext)
- whenever(coverContainer1.context).thenReturn(mockContext)
- whenever(coverContainer2.context).thenReturn(mockContext)
- whenever(coverContainer3.context).thenReturn(mockContext)
- }
-
@After
fun tearDown() {
session.release()
@@ -1488,169 +1390,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
/* ***** END guts tests for the player ***** */
- /* ***** Guts tests for the recommendations ***** */
-
- @Test
- fun recommendations_longClick_isFalse() {
- whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
-
- captor.value.onLongClick(viewHolder.player)
- verify(mediaViewController, never()).openGuts()
- verify(mediaViewController, never()).closeGuts()
- }
-
- @Test
- fun recommendations_longClickWhenGutsClosed_gutsOpens() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
-
- captor.value.onLongClick(viewHolder.player)
- verify(mediaViewController).openGuts()
- verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendations_longClickWhenGutsOpen_gutsCloses() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
-
- captor.value.onLongClick(viewHolder.player)
- verify(mediaViewController, never()).openGuts()
- verify(mediaViewController).closeGuts(false)
- }
-
- @Test
- fun recommendations_cancelButtonClick_animation() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- cancel.callOnClick()
-
- verify(mediaViewController).closeGuts(false)
- }
-
- @Test
- fun recommendations_settingsButtonClick() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- settings.callOnClick()
- verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
-
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(activityStarter).startActivity(captor.capture(), eq(true))
-
- assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
- }
-
- @Test
- fun recommendations_dismissButtonClick() {
- val mediaKey = "key for dismissal"
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData.copy(targetId = mediaKey))
-
- assertThat(dismiss.isEnabled).isEqualTo(true)
- dismiss.callOnClick()
- verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong())
- }
-
- @Test
- fun recommendation_gutsOpen_contentDescriptionIsForGuts() {
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
- player.attachRecommendation(recommendationViewHolder)
-
- val gutsTextString = "gutsText"
- whenever(gutsText.text).thenReturn(gutsTextString)
- player.bindRecommendation(smartspaceData)
-
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description).isEqualTo(gutsTextString)
- }
-
- @Test
- fun recommendation_gutsClosed_contentDescriptionIsForPlayer() {
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
- player.attachRecommendation(recommendationViewHolder)
-
- player.bindRecommendation(smartspaceData)
-
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description)
- .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
- }
-
- @Test
- fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() {
- // Start out open
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
- whenever(gutsText.text).thenReturn("gutsText")
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- // Update to closed by long pressing
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
- reset(viewHolder.player)
-
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
- captor.value.onLongClick(viewHolder.player)
-
- // Then content description is now the player content description
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description)
- .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
- }
-
- @Test
- fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() {
- // Start out closed
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
- val gutsTextString = "gutsText"
- whenever(gutsText.text).thenReturn(gutsTextString)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- // Update to open by long pressing
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
- reset(viewHolder.player)
-
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
- captor.value.onLongClick(viewHolder.player)
-
- // Then content description is now the guts content description
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description).isEqualTo(gutsTextString)
- }
-
- /* ***** END guts tests for the recommendations ***** */
-
@Test
fun actionPlayPauseClick_isLogged() {
val semanticActions =
@@ -1887,578 +1626,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
- fun recommendation_gutsClosed_longPressOpens() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(recommendationViewHolder.recommendations).setOnLongClickListener(captor.capture())
-
- captor.value.onLongClick(recommendationViewHolder.recommendations)
- verify(mediaViewController).openGuts()
- verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendation_settingsButtonClick_isLogged() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- settings.callOnClick()
- verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
-
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(activityStarter).startActivity(captor.capture(), eq(true))
-
- assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
- }
-
- @Test
- fun recommendation_dismissButton_isLogged() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- dismiss.callOnClick()
- verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendation_tapOnCard_isLogged() {
- val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- verify(recommendationViewHolder.recommendations).setOnClickListener(captor.capture())
- captor.value.onClick(recommendationViewHolder.recommendations)
-
- verify(logger).logRecommendationCardTap(eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendation_tapOnItem_isLogged() {
- val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- verify(coverContainer1).setOnClickListener(captor.capture())
- captor.value.onClick(recommendationViewHolder.recommendations)
-
- verify(logger).logRecommendationItemTap(eq(PACKAGE), eq(instanceId), eq(0))
- }
-
- @Test
- fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
- player.attachRecommendation(recommendationViewHolder)
- val icon =
- Icon.createWithResource(context, com.android.settingslib.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(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(recTitle1.text).isEqualTo("")
- verify(mediaViewController, never()).refreshState()
- }
-
- @Test
- fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
- player.attachRecommendation(recommendationViewHolder)
- val icon =
- Icon.createWithResource(context, com.android.settingslib.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(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(recTitle1.text).isEqualTo("")
- verify(mediaViewController, never()).refreshState()
- }
-
- @Test
- fun bindRecommendation_hasTitlesAndSubtitles() {
- player.attachRecommendation(recommendationViewHolder)
-
- val title1 = "Title1"
- val title2 = "Title2"
- val title3 = "Title3"
- val subtitle1 = "Subtitle1"
- val subtitle2 = "Subtitle2"
- val subtitle3 = "Subtitle3"
- val icon =
- Icon.createWithResource(context, com.android.settingslib.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(),
- )
- )
- player.bindRecommendation(data)
-
- assertThat(recTitle1.text).isEqualTo(title1)
- assertThat(recTitle2.text).isEqualTo(title2)
- assertThat(recTitle3.text).isEqualTo(title3)
- assertThat(recSubtitle1.text).isEqualTo(subtitle1)
- assertThat(recSubtitle2.text).isEqualTo(subtitle2)
- assertThat(recSubtitle3.text).isEqualTo(subtitle3)
- }
-
- @Test
- fun bindRecommendation_noTitle_subtitleNotShown() {
- player.attachRecommendation(recommendationViewHolder)
-
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("fake subtitle")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build()
- )
- )
- player.bindRecommendation(data)
-
- assertThat(recSubtitle1.text).isEqualTo("")
- }
-
- @Test
- fun bindRecommendation_someHaveTitles_allTitleViewsShown() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
-
- val icon =
- Icon.createWithResource(context, com.android.settingslib.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(),
- )
- )
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
- }
-
- @Test
- fun bindRecommendation_someHaveSubtitles_allSubtitleViewsShown() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
-
- val icon =
- Icon.createWithResource(context, com.android.settingslib.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("subtitle2")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
- }
-
- @Test
- fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.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,
- com.android.settingslib.R.drawable.ic_3g_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
- }
-
- @Test
- fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("subtitle1")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.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,
- com.android.settingslib.R.drawable.ic_3g_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
- }
-
- @Test
- fun bindRecommendation_setAfterExecutors() {
- val albumArt = getColorIcon(Color.RED)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
- bgExecutor.runAllReady()
- mainExecutor.runAllReady()
-
- verify(recCardTitle).setTextColor(any<Int>())
- verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>())
- verify(coverItem, times(3)).setImageDrawable(any<Drawable>())
- verify(coverItem, times(3)).imageMatrix = any()
- }
-
- @Test
- fun bindRecommendationWithProgressBars() {
- useRealConstraintSets()
- val albumArt = getColorIcon(Color.RED)
- val bundle =
- Bundle().apply {
- putInt(
- MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED,
- )
- putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5)
- }
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(bundle)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
-
- verify(recProgressBar1).setProgress(50)
- verify(recProgressBar1).visibility = View.VISIBLE
- verify(recProgressBar2).visibility = View.GONE
- verify(recProgressBar3).visibility = View.GONE
- assertThat(recSubtitle1.visibility).isEqualTo(View.GONE)
- assertThat(recSubtitle2.visibility).isEqualTo(View.VISIBLE)
- assertThat(recSubtitle3.visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
- useRealConstraintSets()
- val albumArt = getColorIcon(Color.RED)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- // set the screen width less than the width of media controls.
- player.context.resources.configuration.screenWidthDp = 350
- player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
-
- val res = player.context.resources
- val displayAvailableWidth =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
- val recCoverWidth: Int =
- (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
- res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
- val numOfRecs = displayAvailableWidth / recCoverWidth
-
- assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
- recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
- if (index < numOfRecs) {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(container.id))
- .isEqualTo(ConstraintSet.VISIBLE)
- } else {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- }
- }
- }
-
- @Test
- fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
- useRealConstraintSets()
- val albumArt = getColorIcon(Color.RED)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- // set the screen width less than the width of media controls.
- // We should have dp width less than 378 to test. In landscape we should have 2x.
- player.context.resources.configuration.screenWidthDp = 700
- player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
-
- val res = player.context.resources
- val displayAvailableWidth =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
- val recCoverWidth: Int =
- (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
- res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
- val numOfRecs = displayAvailableWidth / recCoverWidth
-
- assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
- recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
- if (index < numOfRecs) {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(container.id))
- .isEqualTo(ConstraintSet.VISIBLE)
- } else {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- }
- }
- }
-
- @Test
- fun addTwoRecommendationGradients_differentStates() {
- // Setup redArtwork and its color scheme.
- val redArt = getColorIcon(Color.RED)
- val redWallpaperColor = player.getWallpaperColor(redArt)
- val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
-
- // Setup greenArt and its color scheme.
- val greenArt = getColorIcon(Color.GREEN)
- val greenWallpaperColor = player.getWallpaperColor(greenArt)
- val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
-
- // Add gradient to both icons.
- val redArtwork = player.addGradientToRecommendationAlbum(redArt, redColorScheme, 10, 10)
- val greenArtwork =
- player.addGradientToRecommendationAlbum(greenArt, greenColorScheme, 10, 10)
-
- // They should have different constant states as they have different gradient color.
- assertThat(redArtwork.getDrawable(1).constantState)
- .isNotEqualTo(greenArtwork.getDrawable(1).constantState)
- }
-
- @Test
fun onButtonClick_playsTouchRipple() {
val semanticActions =
MediaButton(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index e765b6f77155..760f73c726a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -42,7 +42,6 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaViewHolder
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
@@ -79,7 +78,6 @@ class MediaViewControllerTest : SysuiTestCase() {
private val configurationController =
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
- private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
private val clock = FakeSystemClock()
private lateinit var mainExecutor: FakeExecutor
private lateinit var seekBar: SeekBar
@@ -110,9 +108,6 @@ class MediaViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mockCopiedState: TransitionViewState
@Mock private lateinit var detailWidgetState: WidgetState
@Mock private lateinit var controlWidgetState: WidgetState
- @Mock private lateinit var mediaTitleWidgetState: WidgetState
- @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
- @Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
@Mock private lateinit var globalSettings: GlobalSettings
@@ -145,7 +140,7 @@ class MediaViewControllerTest : SysuiTestCase() {
context: Context,
animId: Int,
motionInterpolator: Interpolator?,
- vararg targets: View?
+ vararg targets: View?,
): AnimatorSet {
return mockAnimator
}
@@ -158,7 +153,7 @@ class MediaViewControllerTest : SysuiTestCase() {
fun testOrientationChanged_heightOfPlayerIsUpdated() {
val newConfig = Configuration()
- mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ mediaViewController.attach(player)
// Change the height to see the effect of orientation change.
MediaViewHolder.backgroundIds.forEach { id ->
mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10
@@ -177,30 +172,8 @@ class MediaViewControllerTest : SysuiTestCase() {
}
@Test
- fun testOrientationChanged_heightOfRecCardIsUpdated() {
- val newConfig = Configuration()
-
- mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
- // Change the height to see the effect of orientation change.
- mediaViewController.expandedLayout
- .getConstraint(RecommendationViewHolder.backgroundId)
- .layout
- .mHeight = 10
- newConfig.orientation = ORIENTATION_LANDSCAPE
- configurationController.onConfigurationChanged(newConfig)
-
- assertTrue(
- mediaViewController.expandedLayout
- .getConstraint(RecommendationViewHolder.backgroundId)
- .layout
- .mHeight ==
- context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
- )
- }
-
- @Test
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
- mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ mediaViewController.attach(player)
player.measureState =
TransitionViewState().apply {
this.height = 100
@@ -224,29 +197,8 @@ class MediaViewControllerTest : SysuiTestCase() {
}
@Test
- fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() {
- mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
- recommendation.measureState = TransitionViewState().apply { this.height = 100 }
- mediaHostStateHolder.expansion = 1f
- val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
- val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
- mediaHostStateHolder.measurementInput =
- MeasurementInput(widthMeasureSpec, heightMeasureSpec)
-
- // Test no squish
- mediaHostStateHolder.squishFraction = 1f
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100)
-
- // Test half squish
- mediaHostStateHolder.squishFraction = 0.5f
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100)
- }
-
- @Test
fun testObtainViewState_expandedMatchesParentHeight() {
- mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ mediaViewController.attach(player)
player.measureState =
TransitionViewState().apply {
this.height = 100
@@ -283,7 +235,7 @@ class MediaViewControllerTest : SysuiTestCase() {
.thenReturn(
mutableMapOf(
R.id.media_progress_bar to controlWidgetState,
- R.id.header_artist to detailWidgetState
+ R.id.header_artist to detailWidgetState,
)
)
whenever(mockCopiedState.measureHeight).thenReturn(200)
@@ -311,7 +263,7 @@ class MediaViewControllerTest : SysuiTestCase() {
.thenReturn(
mutableMapOf(
R.id.media_progress_bar to controlWidgetState,
- R.id.header_artist to detailWidgetState
+ R.id.header_artist to detailWidgetState,
)
)
whenever(mockCopiedState.measureHeight).thenReturn(200)
@@ -332,46 +284,6 @@ class MediaViewControllerTest : SysuiTestCase() {
verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
}
- @Test
- fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
- whenever(mockViewState.copy()).thenReturn(mockCopiedState)
- whenever(mockCopiedState.widgetStates)
- .thenReturn(
- mutableMapOf(
- R.id.media_title to mediaTitleWidgetState,
- R.id.media_subtitle to mediaSubTitleWidgetState,
- R.id.media_cover1_container to mediaContainerWidgetState
- )
- )
- whenever(mockCopiedState.measureHeight).thenReturn(360)
- // media container widgets occupy [20, 300]
- whenever(mediaContainerWidgetState.y).thenReturn(20F)
- whenever(mediaContainerWidgetState.height).thenReturn(280)
- whenever(mediaContainerWidgetState.alpha).thenReturn(1F)
- // media title widgets occupy [320, 330]
- whenever(mediaTitleWidgetState.y).thenReturn(320F)
- whenever(mediaTitleWidgetState.height).thenReturn(10)
- whenever(mediaTitleWidgetState.alpha).thenReturn(1F)
- // media subtitle widgets occupy [340, 350]
- whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
- whenever(mediaSubTitleWidgetState.height).thenReturn(10)
- whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F)
-
- // in current beizer, when the progress reach 0.38, the result will be 0.5
- mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
- verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- mediaViewController.squishViewState(mockViewState, 320F / 360F)
- verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
- // media title and media subtitle are in same widget group, should be calculate together and
- // have same alpha
- mediaViewController.squishViewState(mockViewState, 353.8F / 360F)
- verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- mediaViewController.squishViewState(mockViewState, 360F / 360F)
- verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
- verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
- }
-
@EnableSceneContainer
@Test
fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
index f394c805f5b7..8f1d07bac4df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
@@ -855,6 +855,20 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun contentDescriptionUpdated() {
+ var elapsedTimeDesc: CharSequence? = null
+ var durationDesc: CharSequence? = null
+ val listener =
+ object : SeekBarViewModel.ContentDescriptionListener {
+ override fun onContentDescriptionChanged(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ ) {
+ elapsedTimeDesc = elapsedTimeDescription
+ durationDesc = durationDescription
+ }
+ }
+ viewModel.setContentDescriptionListener(listener)
+
// When there is a duration and position
val duration = (1.5 * 60 * 60 * 1000).toLong()
val metadata =
@@ -875,9 +889,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
viewModel.updateController(mockController)
fakeExecutor.runNextReady()
- // Then the content description is set
- val result = viewModel.progress.value!!
-
+ // Then the content description listener gets an update
val expectedProgress =
MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
.formatMeasures(Measure(3, MeasureUnit.SECOND))
@@ -888,7 +900,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
Measure(30, MeasureUnit.MINUTE),
Measure(0, MeasureUnit.SECOND),
)
- assertThat(result.durationDescription).isEqualTo(expectedDuration)
- assertThat(result.elapsedTimeDescription).isEqualTo(expectedProgress)
+ assertThat(elapsedTimeDesc).isEqualTo(expectedProgress)
+ assertThat(durationDesc).isEqualTo(expectedDuration)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 205ccea657df..9ad2235965bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -406,11 +406,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
}
@Override
- int getHeaderIconSize() {
- return 10;
- }
-
- @Override
CharSequence getHeaderText() {
return mHeaderTitle;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index f0902e35b837..f1bf7c0bcf13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -50,6 +50,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -152,9 +153,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
volumePanelGlobalStateInteractor,
mUserTracker);
mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputBroadcastDialog =
- new MediaOutputBroadcastDialog(
- mContext, false, mBroadcastSender, mMediaSwitchingController);
+ mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
+ mBroadcastSender, mMediaSwitchingController, mContext.getMainExecutor(),
+ ThreadUtils.getBackgroundExecutor());
mMediaOutputBroadcastDialog.show();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index d3ecb3d8c944..420fd6e33abc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -53,6 +53,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -455,6 +456,8 @@ public class MediaOutputDialogTest extends SysuiTestCase {
controller,
mDialogTransitionAnimator,
mUiEventLogger,
+ mContext.getMainExecutor(),
+ ThreadUtils.getBackgroundExecutor(),
true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 5c26dac5eb30..798aa428e73e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -60,13 +60,13 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import android.text.TextUtils;
import android.view.View;
import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.media.flags.Flags;
@@ -101,6 +101,9 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -108,7 +111,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaSwitchingControllerTest extends SysuiTestCase {
private static final String TEST_DEVICE_1_ID = "test_device_1_id";
@@ -201,6 +204,17 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
private MediaDescription mMediaDescription;
private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION,
+ Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING);
+ }
+
+ public MediaSwitchingControllerTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() {
mPackageName = mContext.getPackageName();
@@ -260,7 +274,6 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
-
when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID);
when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_FAR);
when(mNearbyDevice2.getMediaRoute2Id()).thenReturn(TEST_DEVICE_2_ID);
@@ -689,7 +702,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.clearMediaItemList();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
int dividerSize = 0;
@@ -1528,7 +1541,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.clearMediaItemList();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
@@ -1546,7 +1559,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.clearMediaItemList();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
@@ -1564,7 +1577,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.clearMediaItemList();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
@@ -1582,7 +1595,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.clearMediaItemList();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
mMediaDevices.clear();
mMediaDevices.add(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java
new file mode 100644
index 000000000000..f6edd49f142f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.media.flags.Flags;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4.class)
+@TestableLooper.RunWithLooper
+public class OutputMediaItemListProxyTest extends SysuiTestCase {
+ private static final String DEVICE_ID_1 = "device_id_1";
+ private static final String DEVICE_ID_2 = "device_id_2";
+ private static final String DEVICE_ID_3 = "device_id_3";
+ private static final String DEVICE_ID_4 = "device_id_4";
+ @Mock private MediaDevice mMediaDevice1;
+ @Mock private MediaDevice mMediaDevice2;
+ @Mock private MediaDevice mMediaDevice3;
+ @Mock private MediaDevice mMediaDevice4;
+
+ private MediaItem mMediaItem1;
+ private MediaItem mMediaItem2;
+ private MediaItem mConnectNewDeviceMediaItem;
+ private OutputMediaItemListProxy mOutputMediaItemListProxy;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION,
+ Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING);
+ }
+
+ public OutputMediaItemListProxyTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMediaDevice1.getId()).thenReturn(DEVICE_ID_1);
+ when(mMediaDevice2.getId()).thenReturn(DEVICE_ID_2);
+ when(mMediaDevice2.isSuggestedDevice()).thenReturn(true);
+ when(mMediaDevice3.getId()).thenReturn(DEVICE_ID_3);
+ when(mMediaDevice4.getId()).thenReturn(DEVICE_ID_4);
+ mMediaItem1 = MediaItem.createDeviceMediaItem(mMediaDevice1);
+ mMediaItem2 = MediaItem.createDeviceMediaItem(mMediaDevice2);
+ mConnectNewDeviceMediaItem = MediaItem.createPairNewDeviceMediaItem();
+
+ mOutputMediaItemListProxy = new OutputMediaItemListProxy(mContext);
+ }
+
+ @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void updateMediaDevices_shouldUpdateMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ // Create the initial output media item list with mMediaDevice2 and mMediaDevice3.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice2, mMediaDevice3),
+ /* selectedDevices */ List.of(mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ // Check the output media items to be
+ // * a media item with the selected mMediaDevice3
+ // * a group divider for suggested devices
+ // * a media item with the mMediaDevice2
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(mMediaDevice3, null, mMediaDevice2);
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList().get(0).isFirstDeviceInGroup())
+ .isEqualTo(Flags.enableOutputSwitcherDeviceGrouping());
+
+ // Update the output media item list with more media devices.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2),
+ /* selectedDevices */ List.of(mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ // Check the output media items to be
+ // * a media item with the selected route mMediaDevice3
+ // * a group divider for suggested devices
+ // * a media item with the route mMediaDevice2
+ // * a group divider for speakers and displays
+ // * a media item with the route mMediaDevice4
+ // * a media item with the route mMediaDevice1
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(
+ mMediaDevice3, null, mMediaDevice2, null, mMediaDevice4, mMediaDevice1);
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList().get(0).isFirstDeviceInGroup())
+ .isEqualTo(Flags.enableOutputSwitcherDeviceGrouping());
+
+ // Update the output media item list where mMediaDevice4 is offline and new selected device.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice2),
+ /* selectedDevices */ List.of(mMediaDevice1, mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ // Check the output media items to be
+ // * a media item with the selected route mMediaDevice3
+ // * a group divider for suggested devices
+ // * a media item with the route mMediaDevice2
+ // * a group divider for speakers and displays
+ // * a media item with the route mMediaDevice1
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(mMediaDevice3, null, mMediaDevice2, null, mMediaDevice1);
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList().get(0).isFirstDeviceInGroup())
+ .isEqualTo(Flags.enableOutputSwitcherDeviceGrouping());
+ }
+
+ @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void updateMediaDevices_multipleSelectedDevices_shouldHaveCorrectDeviceOrdering() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ // Create the initial output media item list with mMediaDevice2 and mMediaDevice3.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice2, mMediaDevice4, mMediaDevice3, mMediaDevice1),
+ /* selectedDevices */ List.of(mMediaDevice1, mMediaDevice2, mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ if (Flags.enableOutputSwitcherDeviceGrouping()) {
+ // When the device grouping is enabled, the order of selected devices are preserved:
+ // * a media item with the selected mMediaDevice2
+ // * a media item with the selected mMediaDevice3
+ // * a media item with the selected mMediaDevice1
+ // * a group divider for speakers and displays
+ // * a media item with the mMediaDevice4
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(
+ mMediaDevice2, mMediaDevice3, mMediaDevice1, null, mMediaDevice4);
+ assertThat(
+ mOutputMediaItemListProxy
+ .getOutputMediaItemList()
+ .get(0)
+ .isFirstDeviceInGroup())
+ .isTrue();
+ } else {
+ // When the device grouping is disabled, the order of selected devices are reverted:
+ // * a media item with the selected mMediaDevice1
+ // * a media item with the selected mMediaDevice3
+ // * a media item with the selected mMediaDevice2
+ // * a group divider for speakers and displays
+ // * a media item with the mMediaDevice4
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(
+ mMediaDevice1, mMediaDevice3, mMediaDevice2, null, mMediaDevice4);
+ }
+
+ // Update the output media item list with a selected device being deselected.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2),
+ /* selectedDevices */ List.of(mMediaDevice2, mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ if (Flags.enableOutputSwitcherDeviceGrouping()) {
+ // When the device grouping is enabled, the order of selected devices are preserved:
+ // * a media item with the selected mMediaDevice2
+ // * a media item with the selected mMediaDevice3
+ // * a media item with the selected mMediaDevice1
+ // * a group divider for speakers and displays
+ // * a media item with the mMediaDevice4
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(
+ mMediaDevice2, mMediaDevice3, mMediaDevice1, null, mMediaDevice4);
+ assertThat(
+ mOutputMediaItemListProxy
+ .getOutputMediaItemList()
+ .get(0)
+ .isFirstDeviceInGroup())
+ .isTrue();
+ } else {
+ // When the device grouping is disabled, the order of selected devices are reverted:
+ // * a media item with the selected mMediaDevice1
+ // * a media item with the selected mMediaDevice3
+ // * a media item with the selected mMediaDevice2
+ // * a group divider for speakers and displays
+ // * a media item with the mMediaDevice4
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(
+ mMediaDevice1, mMediaDevice3, mMediaDevice2, null, mMediaDevice4);
+ }
+
+ // Update the output media item list with a selected device is missing.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice4),
+ /* selectedDevices */ List.of(mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ if (Flags.enableOutputSwitcherDeviceGrouping()) {
+ // When the device grouping is enabled, the order of selected devices are preserved:
+ // * a media item with the selected mMediaDevice3
+ // * a media item with the selected mMediaDevice1
+ // * a group divider for speakers and displays
+ // * a media item with the mMediaDevice4
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(mMediaDevice3, mMediaDevice1, null, mMediaDevice4);
+ assertThat(
+ mOutputMediaItemListProxy
+ .getOutputMediaItemList()
+ .get(0)
+ .isFirstDeviceInGroup())
+ .isTrue();
+ } else {
+ // When the device grouping is disabled, the order of selected devices are reverted:
+ // * a media item with the selected mMediaDevice1
+ // * a media item with the selected mMediaDevice3
+ // * a group divider for speakers and displays
+ // * a media item with the mMediaDevice4
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(mMediaDevice1, mMediaDevice3, null, mMediaDevice4);
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void updateMediaDevices_withConnectNewDeviceMediaItem_shouldUpdateMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ // Create the initial output media item list with a connect new device media item.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice2, mMediaDevice3),
+ /* selectedDevices */ List.of(mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ mConnectNewDeviceMediaItem);
+
+ // Check the output media items to be
+ // * a media item with the selected mMediaDevice3
+ // * a group divider for suggested devices
+ // * a media item with the mMediaDevice2
+ // * a connect new device media item
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList())
+ .contains(mConnectNewDeviceMediaItem);
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(mMediaDevice3, null, mMediaDevice2, null);
+
+ // Update the output media item list without a connect new device media item.
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice2, mMediaDevice3),
+ /* selectedDevices */ List.of(mMediaDevice3),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+
+ // Check the output media items to be
+ // * a media item with the selected mMediaDevice3
+ // * a group divider for suggested devices
+ // * a media item with the mMediaDevice2
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList())
+ .doesNotContain(mConnectNewDeviceMediaItem);
+ assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
+ .containsExactly(mMediaDevice3, null, mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void clearAndAddAll_shouldUpdateMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem1));
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()).containsExactly(mMediaItem1);
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+
+ mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem2));
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()).containsExactly(mMediaItem2);
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+ }
+
+ @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void clear_flagOn_shouldClearMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice1),
+ /* selectedDevices */ List.of(),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+
+ mOutputMediaItemListProxy.clear();
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void clear_flagOff_shouldClearMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem1));
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+
+ mOutputMediaItemListProxy.clear();
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+ }
+
+ @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void removeMutingExpectedDevices_flagOn_shouldClearMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ mOutputMediaItemListProxy.updateMediaDevices(
+ /* devices= */ List.of(mMediaDevice1),
+ /* selectedDevices */ List.of(),
+ /* connectedMediaDevice= */ null,
+ /* needToHandleMutingExpectedDevice= */ false,
+ /* connectNewDeviceMediaItem= */ null);
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+
+ mOutputMediaItemListProxy.removeMutingExpectedDevices();
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+ }
+
+ @DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
+ @Test
+ public void removeMutingExpectedDevices_flagOff_shouldClearMediaItemList() {
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
+
+ mOutputMediaItemListProxy.clearAndAddAll(List.of(mMediaItem1));
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+
+ mOutputMediaItemListProxy.removeMutingExpectedDevices();
+ assertThat(mOutputMediaItemListProxy.getOutputMediaItemList()).containsExactly(mMediaItem1);
+ assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
+ }
+
+ private List<MediaDevice> getMediaDevices(List<MediaItem> mediaItems) {
+ return mediaItems.stream()
+ .map(item -> item.getMediaDevice().orElse(null))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 92b26ea3a8ef..7e42ec7e83b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -30,6 +30,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
+import com.android.compose.theme.PlatformTheme
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -55,19 +56,21 @@ class DragAndDropTest : SysuiTestCase() {
listState: EditTileListState,
onSetTiles: (List<TileSpec>) -> Unit,
) {
- DefaultEditTileGrid(
- listState = listState,
- otherTiles = listOf(),
- columns = 4,
- largeTilesSpan = 4,
- modifier = Modifier.fillMaxSize(),
- onAddTile = {},
- onRemoveTile = {},
- onSetTiles = onSetTiles,
- onResize = { _, _ -> },
- onStopEditing = {},
- onReset = null,
- )
+ PlatformTheme {
+ DefaultEditTileGrid(
+ listState = listState,
+ otherTiles = listOf(),
+ columns = 4,
+ largeTilesSpan = 4,
+ modifier = Modifier.fillMaxSize(),
+ onAddTile = {},
+ onRemoveTile = {},
+ onSetTiles = onSetTiles,
+ onResize = { _, _ -> },
+ onStopEditing = {},
+ onReset = null,
+ )
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
index 8c09b81744d7..9d4a425c678b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
@@ -25,12 +25,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.theme.PlatformTheme
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -54,19 +57,22 @@ class EditModeTest : SysuiTestCase() {
var tiles by remember { mutableStateOf(TestEditTiles) }
val (currentTiles, otherTiles) = tiles.partition { it.tile.isCurrent }
val listState = EditTileListState(currentTiles, columns = 4, largeTilesSpan = 2)
- DefaultEditTileGrid(
- listState = listState,
- otherTiles = otherTiles,
- columns = 4,
- largeTilesSpan = 4,
- modifier = Modifier.fillMaxSize(),
- onAddTile = { tiles = tiles.add(it) },
- onRemoveTile = { tiles = tiles.remove(it) },
- onSetTiles = {},
- onResize = { _, _ -> },
- onStopEditing = {},
- onReset = null,
- )
+
+ PlatformTheme {
+ DefaultEditTileGrid(
+ listState = listState,
+ otherTiles = otherTiles,
+ columns = 4,
+ largeTilesSpan = 4,
+ modifier = Modifier.fillMaxSize(),
+ onAddTile = { tiles = tiles.add(it) },
+ onRemoveTile = { tiles = tiles.remove(it) },
+ onSetTiles = {},
+ onResize = { _, _ -> },
+ onStopEditing = {},
+ onReset = null,
+ )
+ }
}
@Test
@@ -80,7 +86,9 @@ class EditModeTest : SysuiTestCase() {
composeRule.assertCurrentTilesGridContainsExactly(
listOf("tileA", "tileB", "tileC", "tileD_large", "tileE", "tileF")
)
- composeRule.assertAvailableTilesGridContainsExactly(listOf("tileG_large"))
+ composeRule.assertAvailableTilesGridContainsExactly(
+ TestEditTiles.map { it.tile.tileSpec.spec }
+ )
}
@Test
@@ -88,7 +96,8 @@ class EditModeTest : SysuiTestCase() {
composeRule.setContent { EditTileGridUnderTest() }
composeRule.waitForIdle()
- composeRule.onNodeWithContentDescription("tileA").performClick() // Selects
+ // Selects first "tileA", i.e. the one in the current grid
+ composeRule.onAllNodesWithText("tileA").onFirst().performClick()
composeRule.onNodeWithText("Remove").performClick() // Removes
composeRule.waitForIdle()
@@ -96,7 +105,9 @@ class EditModeTest : SysuiTestCase() {
composeRule.assertCurrentTilesGridContainsExactly(
listOf("tileB", "tileC", "tileD_large", "tileE")
)
- composeRule.assertAvailableTilesGridContainsExactly(listOf("tileA", "tileF", "tileG_large"))
+ composeRule.assertAvailableTilesGridContainsExactly(
+ TestEditTiles.map { it.tile.tileSpec.spec }
+ )
}
private fun ComposeContentTestRule.assertCurrentTilesGridContainsExactly(specs: List<String>) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index bd4c5f50eee7..5e76000cc7f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -25,7 +25,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
import androidx.compose.ui.test.performTouchInput
@@ -34,6 +35,7 @@ import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.theme.PlatformTheme
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -60,19 +62,21 @@ class ResizingTest : SysuiTestCase() {
listState: EditTileListState,
onResize: (TileSpec, Boolean) -> Unit,
) {
- DefaultEditTileGrid(
- listState = listState,
- otherTiles = listOf(),
- columns = 4,
- largeTilesSpan = 4,
- modifier = Modifier.fillMaxSize(),
- onAddTile = {},
- onRemoveTile = {},
- onSetTiles = {},
- onResize = onResize,
- onStopEditing = {},
- onReset = null,
- )
+ PlatformTheme {
+ DefaultEditTileGrid(
+ listState = listState,
+ otherTiles = listOf(),
+ columns = 4,
+ largeTilesSpan = 4,
+ modifier = Modifier.fillMaxSize(),
+ onAddTile = {},
+ onRemoveTile = {},
+ onSetTiles = {},
+ onResize = onResize,
+ onStopEditing = {},
+ onReset = null,
+ )
+ }
}
@Test
@@ -85,7 +89,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performCustomAccessibilityActionWithLabel(
context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
)
@@ -103,7 +108,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performCustomAccessibilityActionWithLabel(
context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
)
@@ -121,7 +127,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -141,7 +148,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -161,7 +169,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Resize up
swipeRight(startX = right, endX = right * 2)
@@ -181,7 +190,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Resize down
swipeLeft()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 6f71df5958d9..1b5f032652b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.qs.tiles
+import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.os.Handler
import android.os.Looper
@@ -8,11 +9,11 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
+import android.service.quicksettings.Tile
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.telephony.flags.Flags
import com.android.settingslib.Utils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
@@ -82,6 +83,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
whenever(qsHost.context).thenReturn(mContext)
+ whenever(bluetoothController.canConfigBluetooth()).thenReturn(true)
tile =
FakeBluetoothTile(
@@ -257,6 +259,38 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
.removeOnMetadataChangedListener(eq(cachedDevice), any())
}
+ @Test
+ @EnableFlags(QSComposeFragment.FLAG_NAME)
+ fun disableBluetooth_transientTurningOff() {
+ enableBluetooth()
+ tile.refreshState()
+ testableLooper.processAllMessages()
+
+ tile.handleSecondaryClick(null)
+ testableLooper.processAllMessages()
+
+ val state = tile.state
+
+ assertThat(state.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(state.isTransient).isTrue()
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_off))
+ }
+
+ @Test
+ @EnableFlags(QSComposeFragment.FLAG_NAME)
+ fun turningOffState() {
+ setBluetoothTurningOff()
+
+ tile.refreshState()
+ testableLooper.processAllMessages()
+
+ val state = tile.state
+
+ assertThat(state.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(state.isTransient).isTrue()
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_off))
+ }
+
private class FakeBluetoothTile(
qsHost: QSHost,
uiEventLogger: QsEventLogger,
@@ -318,6 +352,13 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
whenever(bluetoothController.isBluetoothConnecting).thenReturn(true)
}
+ fun setBluetoothTurningOff() {
+ whenever(bluetoothController.isBluetoothConnected).thenReturn(false)
+ whenever(bluetoothController.isBluetoothConnecting).thenReturn(false)
+ whenever(bluetoothController.isBluetoothEnabled).thenReturn(false)
+ whenever(bluetoothController.bluetoothState).thenReturn(BluetoothAdapter.STATE_TURNING_OFF)
+ }
+
fun addConnectedDevice(device: CachedBluetoothDevice) {
whenever(bluetoothController.connectedDevices).thenReturn(listOf(device))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
index c20a801cd5e3..356d445ab4d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
@@ -40,9 +40,9 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.android.wifitrackerlib.WifiEntry
@@ -66,7 +66,7 @@ import org.mockito.kotlin.whenever
@EnableFlags(Flags.FLAG_QS_TILE_DETAILED_VIEW)
@UiThreadTest
class InternetDetailsContentManagerTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val handler: Handler = kosmos.fakeExecutorHandler
private val scope: CoroutineScope = mock<CoroutineScope>()
private val telephonyManager: TelephonyManager = kosmos.telephonyManager
@@ -103,6 +103,8 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
whenever(internetWifiEntry.hasInternetAccess()).thenReturn(true)
whenever(wifiEntries.size).thenReturn(1)
whenever(internetDetailsContentController.getDialogTitleText()).thenReturn(TITLE)
+ whenever(internetDetailsContentController.getSubtitleText(ArgumentMatchers.anyBoolean()))
+ .thenReturn("")
whenever(internetDetailsContentController.getMobileNetworkTitle(ArgumentMatchers.anyInt()))
.thenReturn(MOBILE_NETWORK_TITLE)
whenever(
@@ -128,15 +130,13 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
internetDetailsContentController,
canConfigMobileData = true,
canConfigWifi = true,
- coroutineScope = scope,
- context = mContext,
uiEventLogger = mock<UiEventLogger>(),
handler = handler,
backgroundExecutor = bgExecutor,
keyguard = keyguard,
)
- internetDetailsContentManager.bind(contentView)
+ internetDetailsContentManager.bind(contentView, scope)
internetDetailsContentManager.adapter = internetAdapter
internetDetailsContentManager.connectedWifiEntry = internetWifiEntry
internetDetailsContentManager.wifiEntriesCount = wifiEntries.size
@@ -777,6 +777,26 @@ class InternetDetailsContentManagerTest : SysuiTestCase() {
}
}
+ @Test
+ fun updateTitleAndSubtitle() {
+ assertThat(internetDetailsContentManager.title).isEqualTo("Internet")
+ assertThat(internetDetailsContentManager.subTitle).isEqualTo("")
+
+ whenever(internetDetailsContentController.getDialogTitleText()).thenReturn("New title")
+ whenever(internetDetailsContentController.getSubtitleText(ArgumentMatchers.anyBoolean()))
+ .thenReturn("New subtitle")
+
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(internetDetailsContentManager.title).isEqualTo("New title")
+ assertThat(internetDetailsContentManager.subTitle).isEqualTo("New subtitle")
+ }
+ }
+
companion object {
private const val TITLE = "Internet"
private const val MOBILE_NETWORK_TITLE = "Mobile Title"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
index cf54df8565d3..997cf417fe10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
@@ -22,6 +22,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.deviceStateManager
import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor
@@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -59,6 +61,7 @@ class RearDisplayCoreStartableTest : SysuiTestCase() {
fakeRearDisplayStateInteractor,
kosmos.rearDisplayInnerDialogDelegateFactory,
kosmos.testScope,
+ kosmos.keyguardUpdateMonitor,
)
@Before
@@ -96,6 +99,26 @@ class RearDisplayCoreStartableTest : SysuiTestCase() {
}
}
+ @Test
+ @EnableFlags(FLAG_DEVICE_STATE_RDM_V2)
+ fun testDialogResumesAfterKeyguardGone() =
+ kosmos.runTest {
+ impl.use {
+ it.start()
+ fakeRearDisplayStateInteractor.emitRearDisplay()
+
+ verify(mockDialog).show()
+
+ it.keyguardCallback.onKeyguardVisibilityChanged(true)
+ // Do not need to check that the dialog is dismissed, since SystemUIDialog
+ // implementation handles that. We just toggle keyguard here so that the flow
+ // emits.
+
+ it.keyguardCallback.onKeyguardVisibilityChanged(false)
+ verify(mockDialog, times(2)).show()
+ }
+ }
+
private class FakeRearDisplayStateInteractor(private val kosmos: Kosmos) :
RearDisplayStateInteractor {
private val stateFlow = MutableSharedFlow<RearDisplayStateInteractor.State>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
index 40547c2787ac..e0118b18ff64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
@@ -24,6 +24,7 @@ import android.os.PowerManager
import android.os.UserManager
import android.testing.TestableContext
import android.testing.TestableLooper
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.app.AssistUtils
@@ -35,8 +36,8 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
-import com.android.systemui.model.SysUiState
-import com.android.systemui.model.sceneContainerPlugin
+import com.android.systemui.log.assertLogsWtf
+import com.android.systemui.model.sysUiState
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.process.ProcessWrapper
@@ -67,6 +68,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
@@ -93,7 +95,7 @@ class LauncherProxyServiceTest : SysuiTestCase() {
@Mock private lateinit var processWrapper: ProcessWrapper
private val displayTracker = FakeDisplayTracker(mContext)
private val fakeSystemClock = FakeSystemClock()
- private val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
+ private val sysUiState = kosmos.sysUiState
private val wakefulnessLifecycle =
WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
@@ -165,7 +167,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -175,7 +178,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -185,7 +189,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -197,7 +202,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -216,7 +222,7 @@ class LauncherProxyServiceTest : SysuiTestCase() {
`when`(processWrapper.isSystemUser).thenReturn(false)
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false)
val spyContext = spy(context)
- val ops = createLauncherProxyService(spyContext)
+ val ops = assertLogsWtf { createLauncherProxyService(spyContext) }.result
ops.startConnectionToCurrentUser()
verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
}
@@ -238,7 +244,7 @@ class LauncherProxyServiceTest : SysuiTestCase() {
`when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true)
`when`(userManager.isUserForeground()).thenReturn(true)
val spyContext = spy(context)
- val ops = createLauncherProxyService(spyContext)
+ val ops = assertLogsWtf { createLauncherProxyService(spyContext) }.result
ops.startConnectionToCurrentUser()
verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java
new file mode 100644
index 000000000000..826c54787662
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ringtone;
+
+import static org.junit.Assert.assertThrows;
+
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RingtonePlayerTest extends SysuiTestCase {
+
+ private static final String TAG = "RingtonePlayerTest";
+
+ @Test
+ public void testRingtonePlayerUriUserCheck() {
+ // temporarily skipping this test
+ Log.i(TAG, "skipping testRingtonePlayerUriUserCheck");
+ return;
+
+ // TODO change how IRingtonePlayer is created
+// android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer();
+// final AudioAttributes aa = new AudioAttributes.Builder()
+// .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build();
+// // get a UserId that doesn't belong to mine
+// final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0;
+// // build a URI that I shouldn't have access to
+// final Uri uri = new Uri.Builder()
+// .scheme("content").authority(otherUserId + "@media")
+// .appendPath("external").appendPath("downloads")
+// .appendPath("bogusPathThatDoesNotMatter.mp3")
+// .build();
+// if (android.media.audio.Flags.ringtoneUserUriCheck()) {
+// assertThrows(SecurityException.class, () ->
+// irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/)
+// );
+//
+// assertThrows(SecurityException.class, () ->
+// irp.getTitle(uri));
+// }
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 600572545d55..1f189a540aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -35,6 +35,10 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -43,9 +47,13 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class ActionIntentCreatorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
val context = mock<Context>()
val packageManager = mock<PackageManager>()
- private val actionIntentCreator = ActionIntentCreator(context, packageManager)
+ private val actionIntentCreator =
+ ActionIntentCreator(context, packageManager, testScope.backgroundScope, mainDispatcher)
@Test
fun testCreateShare() {
@@ -132,7 +140,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEditLegacy() {
+ fun testCreateEditLegacy() = runTest {
val uri = Uri.parse("content://fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -155,7 +163,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEditLegacy_embeddedUserIdRemoved() {
+ fun testCreateEditLegacy_embeddedUserIdRemoved() = runTest {
val uri = Uri.parse("content://555@fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -166,7 +174,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEditLegacy_withEditor() {
+ fun testCreateEditLegacy_withEditor() = runTest {
val uri = Uri.parse("content://fake")
val component = ComponentName("com.android.foo", "com.android.foo.Something")
@@ -180,7 +188,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit() {
+ fun testCreateEdit() = runTest {
val uri = Uri.parse("content://fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -203,7 +211,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_embeddedUserIdRemoved() {
+ fun testCreateEdit_embeddedUserIdRemoved() = runTest {
val uri = Uri.parse("content://555@fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -214,7 +222,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_withPreferredEditorEnabled() {
+ fun testCreateEdit_withPreferredEditorEnabled() = runTest {
val uri = Uri.parse("content://fake")
val fallbackComponent = ComponentName("com.android.foo", "com.android.foo.Something")
val preferredComponent = ComponentName("com.android.bar", "com.android.bar.Something")
@@ -243,7 +251,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_withPreferredEditorDisabled() {
+ fun testCreateEdit_withPreferredEditorDisabled() = runTest {
val uri = Uri.parse("content://fake")
val fallbackComponent = ComponentName("com.android.foo", "com.android.foo.Something")
val preferredComponent = ComponentName("com.android.bar", "com.android.bar.Something")
@@ -266,7 +274,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_withFallbackEditor() {
+ fun testCreateEdit_withFallbackEditor() = runTest {
val uri = Uri.parse("content://fake")
val component = ComponentName("com.android.foo", "com.android.foo.Something")
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
index a64ff321cd4d..11aaeefe8214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -134,10 +134,7 @@ class BrightnessDialogTest(val flags: FlagsParameterization) : SysuiTestCase() {
val frame = activityRule.activity.requireViewById<View>(viewId)
val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
- val horizontalMargin =
- activityRule.activity.resources.getDimensionPixelSize(
- R.dimen.notification_side_paddings
- )
+ val horizontalMargin = 0
assertThat(lp.leftMargin).isEqualTo(horizontalMargin)
assertThat(lp.rightMargin).isEqualTo(horizontalMargin)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index e42bf19663a5..f72645eb8596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -23,7 +23,6 @@ import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
-import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -52,8 +51,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.assertLogsWtf
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractorPassThrough
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
@@ -96,7 +97,6 @@ import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
-import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -245,7 +245,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
notificationLaunchAnimationInteractor,
featureFlagsClassic,
fakeClock,
- sysUIKeyEventHandler,
+ WindowRootViewKeyEventHandler({ sysUIKeyEventHandler }, falsingCollector),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -414,7 +414,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
// THEN move is ignored, down is handled, and window is notified
assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse()
- assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+ assertLogsWtf {
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+ }
verify(notificationShadeWindowController).setLaunchingActivity(false)
}
@@ -591,34 +593,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
}
@Test
- fun forwardsDispatchKeyEvent() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
- interactionEventHandler.dispatchKeyEvent(keyEvent)
- verify(sysUIKeyEventHandler).dispatchKeyEvent(keyEvent)
- }
-
- @Test
- fun forwardsDispatchKeyEventPreIme() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
- interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
- verify(sysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent)
- }
-
- @Test
- fun forwardsInterceptMediaKey() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
- interactionEventHandler.interceptMediaKey(keyEvent)
- verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
- }
-
- @Test
- fun forwardsCollectKeyEvent() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
- interactionEventHandler.collectKeyEvent(keyEvent)
- assertEquals(keyEvent, falsingCollector.lastKeyEvent)
- }
-
- @Test
fun cancelCurrentTouch_callsDragDownHelper() {
underTest.cancelCurrentTouch()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
index 8418598c256b..116a2caa6dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
@@ -19,9 +19,9 @@ package com.android.systemui.shared.condition
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.condition.Condition.START_EAGERLY
-import com.android.systemui.shared.condition.Condition.START_LAZILY
-import com.android.systemui.shared.condition.Condition.START_WHEN_NEEDED
+import com.android.systemui.shared.condition.Condition.Companion.START_EAGERLY
+import com.android.systemui.shared.condition.Condition.Companion.START_LAZILY
+import com.android.systemui.shared.condition.Condition.Companion.START_WHEN_NEEDED
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -40,7 +40,7 @@ class CombinedConditionTest : SysuiTestCase() {
scope: CoroutineScope,
initialValue: Boolean?,
overriding: Boolean = false,
- @StartStrategy private val startStrategy: Int = START_WHEN_NEEDED,
+ @StartStrategy override val startStrategy: Int = START_WHEN_NEEDED,
) : Condition(scope, initialValue, overriding) {
private var _started = false
val started: Boolean
@@ -54,10 +54,6 @@ class CombinedConditionTest : SysuiTestCase() {
_started = false
}
- override fun getStartStrategy(): Int {
- return startStrategy
- }
-
fun setValue(value: Boolean?) {
value?.also { updateCondition(value) } ?: clearCondition()
}
@@ -75,13 +71,8 @@ class CombinedConditionTest : SysuiTestCase() {
val combinedCondition =
CombinedCondition(
scope = this,
- conditions =
- listOf(
- eagerCondition,
- lazyCondition,
- startWhenNeededCondition,
- ),
- operand = Evaluator.OP_OR
+ conditions = listOf(eagerCondition, lazyCondition, startWhenNeededCondition),
+ operand = Evaluator.OP_OR,
)
val callback = Condition.Callback {}
@@ -124,13 +115,8 @@ class CombinedConditionTest : SysuiTestCase() {
val combinedCondition =
CombinedCondition(
scope = this,
- conditions =
- listOf(
- startWhenNeededCondition,
- lazyCondition,
- eagerCondition,
- ),
- operand = Evaluator.OP_AND
+ conditions = listOf(startWhenNeededCondition, lazyCondition, eagerCondition),
+ operand = Evaluator.OP_AND,
)
val callback = Condition.Callback {}
@@ -175,7 +161,7 @@ class CombinedConditionTest : SysuiTestCase() {
FakeCondition(
scope = this,
initialValue = false,
- startStrategy = START_WHEN_NEEDED
+ startStrategy = START_WHEN_NEEDED,
)
}
.toList()
@@ -214,7 +200,7 @@ class CombinedConditionTest : SysuiTestCase() {
FakeCondition(
scope = this,
initialValue = false,
- startStrategy = START_WHEN_NEEDED
+ startStrategy = START_WHEN_NEEDED,
)
}
.toList()
@@ -262,7 +248,7 @@ class CombinedConditionTest : SysuiTestCase() {
FakeCondition(
scope = this,
initialValue = false,
- startStrategy = START_WHEN_NEEDED
+ startStrategy = START_WHEN_NEEDED,
)
}
.toList()
@@ -300,9 +286,9 @@ class CombinedConditionTest : SysuiTestCase() {
overridingCondition1,
lazyCondition,
startWhenNeededCondition,
- overridingCondition2
+ overridingCondition2,
),
- operand = Evaluator.OP_OR
+ operand = Evaluator.OP_OR,
)
val callback = Condition.Callback {}
@@ -414,11 +400,7 @@ class CombinedConditionTest : SysuiTestCase() {
fun testEmptyConditions() = runSelfCancelingTest {
for (operand in intArrayOf(Evaluator.OP_OR, Evaluator.OP_AND)) {
val combinedCondition =
- CombinedCondition(
- scope = this,
- conditions = emptyList(),
- operand = operand,
- )
+ CombinedCondition(scope = this, conditions = emptyList(), operand = operand)
val callback = Condition.Callback {}
combinedCondition.addCallback(callback)
@@ -435,9 +417,7 @@ class CombinedConditionTest : SysuiTestCase() {
* Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
* is then automatically canceled and cleaned-up.
*/
- private fun runSelfCancelingTest(
- block: suspend CoroutineScope.() -> Unit,
- ) =
+ private fun runSelfCancelingTest(block: suspend CoroutineScope.() -> Unit) =
runBlocking(IMMEDIATE) {
val scope = CoroutineScope(coroutineContext + Job())
block(scope)
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 b7040ee2a11e..020b5dd054b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -61,10 +62,6 @@ import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
@@ -265,7 +262,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(statusbarStateController, never()).setState(anyInt())
verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
verify(centralSurfaces)
- .showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
+ .showBouncerWithDimissAndCancelIfKeyguard(nullable(), nullable())
}
@Test
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 81213caaa5f4..3570cf827808 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
@@ -77,7 +77,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.time.FakeSystemClock;
@@ -1795,7 +1794,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
// THEN an exception is NOT thrown directly, but a WTF IS logged.
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
});
@@ -1818,7 +1817,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_2);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
Assert.assertThrows(IllegalStateException.class, () -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
@@ -1844,13 +1843,13 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_2);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
});
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
// Note: dispatchBuild itself triggers a non-reentrant pipeline run.
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
@@ -1874,7 +1873,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_1);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
});
@@ -1897,7 +1896,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_1);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
Assert.assertThrows(IllegalStateException.class, () -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
@@ -1922,7 +1921,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_2);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
});
@@ -1945,7 +1944,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_2);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
Assert.assertThrows(IllegalStateException.class, () -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
@@ -1970,7 +1969,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_2);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
});
@@ -1993,7 +1992,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(0, PACKAGE_2);
invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
- LogAssertKt.assertLogsWtfs(() -> {
+ LogAssertKt.assertRunnableLogsWtfs(() -> {
Assert.assertThrows(IllegalStateException.class, () -> {
dispatchBuild();
runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
index 3937d3d46d68..ff17a362eb32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
@@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
@@ -97,7 +96,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
fun themeChangePropagatesToEntry() {
configurationListener.onThemeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
}
@@ -105,7 +103,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
fun densityChangePropagatesToEntry() {
configurationListener.onDensityOrFontScaleChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
}
@@ -129,7 +126,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
verify(entry).row
verify(row).onUiModeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
clearInvocations(entry, row)
@@ -160,7 +156,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
verify(entry).row
verify(row).onUiModeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
clearInvocations(entry, row)
@@ -196,14 +191,7 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
verify(entry).row
verify(row).onUiModeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
clearInvocations(entry, row)
}
-
- private fun checkGutsExposedCalled() {
- if (!Flags.notificationUndoGutsOnConfigChanged()) {
- verify(entry).areGutsExposed()
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index acfa94a0218b..00ee893e0e4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row;
import static android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES;
+import static com.android.systemui.log.LogAssertKt.assertRunnableLogsWtf;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG;
import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE;
@@ -60,6 +61,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.CachingIconView;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
@@ -67,13 +69,21 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
@@ -935,11 +945,11 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPromotedOngoing(true);
+ setRowPromotedOngoing(row);
row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
@@ -948,11 +958,11 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPromotedOngoing(true);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(false);
// THEN
@@ -960,11 +970,11 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPromotedOngoing(true);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
// THEN
@@ -972,12 +982,12 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded()
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPromotedOngoing(true);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
row.setIgnoreLockscreenConstraints(true);
@@ -985,13 +995,31 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
assertThat(row.isExpanded()).isTrue();
}
+ private static void setRowPromotedOngoing(ExpandableNotificationRow row) {
+ final NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ if (NotificationBundleUi.isEnabled()) {
+ final EntryAdapter entryAdapter = new NotificationEntryAdapter(
+ mock(NotificationActivityStarter.class),
+ mock(MetricsLogger.class),
+ mock(PeopleNotificationIdentifier.class),
+ mock(NotificationIconStyleProvider.class),
+ mock(VisualStabilityCoordinator.class),
+ mock(NotificationActionClickManager.class),
+ entry);
+ row.setEntryAdapter(entryAdapter);
+ } else {
+ row.setEntry(entry);
+ }
+ }
+
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded()
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPromotedOngoing(true);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
row.setSaveSpaceOnLockscreen(true);
@@ -1000,12 +1028,12 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded()
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- row.setPromotedOngoing(true);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
row.setSaveSpaceOnLockscreen(false);
@@ -1128,6 +1156,30 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
assertThat(row.mustStayOnScreen()).isFalse();
}
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void hasStatusBarChipDuringHeadsUpAnimation_flagOff_false() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ assertRunnableLogsWtf(() -> row.setHasStatusBarChipDuringHeadsUpAnimation(true));
+
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void hasStatusBarChipDuringHeadsUpAnimation_flagOn_returnsValue() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(true);
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isTrue();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(false);
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+ }
+
private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
Drawable rightIconDrawable) {
ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index c874bc6056c6..5d7b3edc457b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row
import android.annotation.DimenRes
import android.content.res.Resources
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -88,14 +89,11 @@ class NotificationContentViewTest : SysuiTestCase() {
spy(
when (NotificationBundleUi.isEnabled) {
true -> {
- ExpandableNotificationRow(
- mContext,
- /* attrs= */ null,
- UserHandle.CURRENT
- ).apply {
- entry = mockEntry
- entryAdapter = mockEntryAdapter
- }
+ ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT)
+ .apply {
+ entry = mockEntry
+ entryAdapter = mockEntryAdapter
+ }
}
false -> {
ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
@@ -402,6 +400,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should not be shown for the given NotificationEntry
@@ -429,6 +428,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should be shown for the given NotificationEntry
@@ -458,6 +458,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
val mockNotificationEntry = createMockNotificationEntry()
@@ -486,6 +487,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
val mockNotificationEntry = createMockNotificationEntry()
@@ -514,7 +516,7 @@ class NotificationContentViewTest : SysuiTestCase() {
// Given: controller says bubbles are enabled for the user
view.setBubblesEnabledForUser(true)
- // Then: bottom margin of actionListMarginTarget should not change, still be 20
+ // Then: bottom margin of actionListMarginTarget should be changed to 0
assertEquals(0, getMarginBottom(actionListMarginTarget))
}
@@ -628,8 +630,7 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(sbnMock.user).thenReturn(userMock)
}
- private fun createMockNotificationEntryAdapter() =
- mock<EntryAdapter>()
+ private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>()
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
val outerLayout = LinearLayout(mContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
index 1cadb3c0a909..e1bab8ec47e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationCustomContentMemoryVerifierTest.java
@@ -46,6 +46,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.notification.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogAssertKt;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -173,8 +174,10 @@ public class NotificationCustomContentMemoryVerifierTest extends SysuiTestCase {
public void satisfiesMemoryLimits_viewWithoutCustomNotificationRoot_returnsTrue() {
NotificationEntry entry = new NotificationEntryBuilder().build();
View view = new FrameLayout(mContext);
- assertThat(NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry))
- .isTrue();
+ LogAssertKt.assertRunnableLogsWtf(() -> {
+ assertThat(NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry))
+ .isTrue();
+ });
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 0c0ef9d5edfe..10de86644015 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -67,6 +67,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor
import com.android.systemui.statusbar.notification.row.icon.appIconProvider
import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -146,6 +147,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
@Mock private lateinit var notificationManager: INotificationManager
@Mock private lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var packageDemotionInteractor: PackageDemotionInteractor
@Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
@Mock private lateinit var contextTracker: UserContextProvider
@Mock private lateinit var bubblesManager: BubblesManager
@@ -185,6 +187,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
launcherApps,
shortcutManager,
channelEditorDialogController,
+ packageDemotionInteractor,
contextTracker,
assistantFeedbackController,
Optional.of(bubblesManager),
@@ -297,45 +300,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
}
@Test
- fun testChangeDensityOrFontScale() {
- val guts = spy(NotificationGuts(mContext))
- whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
- handler.post((invocation.arguments[0] as Runnable))
- null
- }
-
- // Test doesn't support animation since the guts view is not attached.
- doNothing()
- .whenever(guts)
- .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
- val realRow = createTestNotificationRow()
- val menuItem = createTestMenuItem(realRow)
- val row = spy(realRow)
- whenever(row!!.windowToken).thenReturn(Binder())
- whenever(row.guts).thenReturn(guts)
- doNothing().whenever(row).ensureGutsInflated()
- val realEntry = realRow!!.entry
- val entry = spy(realEntry)
- whenever(entry.row).thenReturn(row)
- whenever(entry.getGuts()).thenReturn(guts)
- Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
- executor.runAllReady()
- verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
-
- // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
- verify(row).setGutsView(any())
- row.onDensityOrFontScaleChanged()
- gutsManager.onDensityOrFontScaleChanged(entry)
- executor.runAllReady()
- gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
- verify(guts)
- .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
-
- // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
- verify(row, times(2)).setGutsView(any())
- }
-
- @Test
fun testAppOpsSettingsIntent_camera() {
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_CAMERA)
@@ -427,6 +391,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -438,6 +403,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ eq(packageDemotionInteractor),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -447,7 +413,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
any<UiEventLogger>(),
eq(true),
eq(false),
- eq(true), /* wasShownHighPriority */
+ eq(true),
+ eq(true),
eq(assistantFeedbackController),
any<MetricsLogger>(),
any<View.OnClickListener>(),
@@ -462,6 +429,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -473,6 +441,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ eq(packageDemotionInteractor),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -482,7 +451,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
any<UiEventLogger>(),
eq(true),
eq(false),
- eq(false), /* wasShownHighPriority */
+ eq(true), /* wasShownHighPriority */
+ eq(false),
eq(assistantFeedbackController),
any<MetricsLogger>(),
any<View.OnClickListener>(),
@@ -497,6 +467,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -508,6 +479,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
eq(iconStyleProvider),
eq(onUserInteractionCallback),
eq(channelEditorDialogController),
+ eq(packageDemotionInteractor),
eq(statusBarNotification.packageName),
any<NotificationChannel>(),
eq(entry),
@@ -517,7 +489,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
any<UiEventLogger>(),
eq(true),
eq(false),
- eq(false), /* wasShownHighPriority */
+ eq(true), /* wasShownHighPriority */
+ eq(false),
eq(assistantFeedbackController),
any<MetricsLogger>(),
any<View.OnClickListener>(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
index 86689cb88569..d0357603665d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
@@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.row.shared.IconData
import com.android.systemui.statusbar.notification.row.shared.ImageModel
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider.ImageSizeClass.SmallSquare
import com.google.common.truth.Truth.assertThat
@@ -38,20 +39,22 @@ class RowImageInflaterTest : SysuiTestCase() {
private val resIcon2 = Icon.createWithResource(context, android.R.drawable.ic_delete)
private val badUriIcon = Icon.createWithContentUri("content://com.test/does_not_exist")
+ private var latestImageModelIndex: ImageModelIndex? = null
+
@Before
fun setUp() {
- rowImageInflater = RowImageInflater.newInstance(null)
+ rowImageInflater = RowImageInflater.newInstance(null, reinflating = false)
}
@Test
fun getNewImageIndex_returnsNullWhenUnused() {
- assertThat(rowImageInflater.getNewImageIndex()).isNull()
+ assertThat(getNewImageIndex()).isNull()
}
@Test
fun getNewImageIndex_returnsEmptyIndexWhenZeroImagesLoaded() {
assertThat(getImageModelsForIcons()).isEmpty()
- val result = rowImageInflater.getNewImageIndex()
+ val result = getNewImageIndex()
assertThat(result).isNotNull()
assertThat(result?.contentsForTesting).isEmpty()
}
@@ -59,7 +62,7 @@ class RowImageInflaterTest : SysuiTestCase() {
@Test
fun getNewImageIndex_returnsSingleImageWhenOneImageLoaded() {
assertThat(getImageModelsForIcons(resIcon1)).hasSize(1)
- val result = rowImageInflater.getNewImageIndex()
+ val result = getNewImageIndex()
assertThat(result).isNotNull()
assertThat(result?.contentsForTesting).hasSize(1)
}
@@ -85,7 +88,7 @@ class RowImageInflaterTest : SysuiTestCase() {
assertThat(providedModels[3].drawable).isNotNull()
// VERIFY the returned index has all 3 entries, 2 of which have drawables
- val indexGen1 = rowImageInflater.getNewImageIndex()
+ val indexGen1 = getNewImageIndex()
assertThat(indexGen1).isNotNull()
assertThat(indexGen1?.contentsForTesting).hasSize(3)
assertThat(indexGen1?.contentsForTesting?.mapNotNull { it.drawable }).hasSize(2)
@@ -96,7 +99,7 @@ class RowImageInflaterTest : SysuiTestCase() {
exampleFirstGeneration()
// THEN start a new generation of the inflation
- rowImageInflater = RowImageInflater.newInstance(rowImageInflater.getNewImageIndex())
+ rowImageInflater = RowImageInflater.newInstance(getNewImageIndex(), reinflating = false)
getNewImageIndex_returnsEmptyIndexWhenZeroImagesLoaded()
}
@@ -104,13 +107,47 @@ class RowImageInflaterTest : SysuiTestCase() {
@Test
fun exampleSecondGeneration_whichLoadsOneImage() {
exampleFirstGeneration()
+ val gen1Index = latestImageModelIndex!!
// THEN start a new generation of the inflation
- rowImageInflater = RowImageInflater.newInstance(rowImageInflater.getNewImageIndex())
+ rowImageInflater = RowImageInflater.newInstance(gen1Index, reinflating = false)
getNewImageIndex_returnsSingleImageWhenOneImageLoaded()
+ val gen2Index = latestImageModelIndex!!
+
+ // VERIFY that the drawable was copied from the previous index
+ val gen1model = gen1Index.findModel(resIcon1)
+ val gen2model = gen2Index.findModel(resIcon1)
+ assertThat(gen2model).isNotSameInstanceAs(gen1model)
+ assertThat(gen2model.drawable).isSameInstanceAs(gen1model.drawable)
}
+ @Test
+ fun exampleSecondGeneration_reinflating_whichLoadsOneImage() {
+ exampleFirstGeneration()
+ val gen1Index = latestImageModelIndex!!
+
+ // THEN start a new generation of the inflation
+ rowImageInflater = RowImageInflater.newInstance(gen1Index, reinflating = true)
+
+ getNewImageIndex_returnsSingleImageWhenOneImageLoaded()
+ val gen2Index = latestImageModelIndex!!
+
+ // VERIFY that the drawable was reloaded rather than copied from the previous index
+ val gen1model = gen1Index.findModel(resIcon1)
+ val gen2model = gen2Index.findModel(resIcon1)
+ assertThat(gen2model).isNotSameInstanceAs(gen1model)
+ assertThat(gen2model.drawable).isNotSameInstanceAs(gen1model.drawable)
+ }
+
+ private fun ImageModelIndex.findModel(icon: Icon): LazyImage =
+ IconData.fromIcon(icon)
+ .let { iconData -> contentsForTesting.find { it.icon == iconData } }
+ .also { assertThat(it).isNotNull() }!!
+
+ private fun getNewImageIndex(): ImageModelIndex? =
+ rowImageInflater.getNewImageIndex().also { latestImageModelIndex = it }
+
private fun getImageModelsForIcons(vararg icons: Icon): List<ImageModel> {
val provider = rowImageInflater.useForContentModel()
return icons.map { icon ->
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 2a58890f8767..8fb2a245921a 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
@@ -82,6 +82,7 @@ import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -92,6 +93,7 @@ import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShade
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
@@ -1260,7 +1262,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN headsUpAnimatingAway is true
verify(headsUpAnimatingAwayListener).accept(true);
@@ -1269,6 +1272,51 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
@EnableSceneContainer
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOff_statusBarChipNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true);
+
+ verify(row, never()).setHasStatusBarChipDuringHeadsUpAnimation(anyBoolean());
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToFalse() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
+
+ verify(row).setHasStatusBarChipDuringHeadsUpAnimation(false);
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToTrue() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true);
+
+ verify(row).setHasStatusBarChipDuringHeadsUpAnimation(true);
+ }
+
+ @Test
+ @EnableSceneContainer
public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
// GIVEN NSSL would be ready for HUN animations, BUT it is expanded
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1279,7 +1327,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN nothing happens
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1294,10 +1343,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// BUT there is a pending appear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN nothing happens
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1313,7 +1364,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// THEN headsUpAnimatingWay is not set
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1335,7 +1387,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
when(row.getEntry()).thenReturn(entry);
// WHEN we generate an add event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// THEN nothing happens
assertThat(mStackScroller.isAddOrRemoveAnimationPending()).isFalse();
@@ -1350,7 +1403,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// AND there is a HUN animating away
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
// WHEN the child animations are finished
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 89b7bee7f286..a3616d20e11f 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
@@ -128,6 +128,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.media.NotificationMediaManager;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.plugins.ActivityStarter;
@@ -164,7 +165,6 @@ import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
index 2e65478714af..12f7af106d10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
@@ -108,7 +108,7 @@ class FoldStateListenerTest : SysuiTestCase() {
private fun setGoToSleepStates(vararg states: Int) {
mContext.orCreateTestableResources.addOverride(
R.array.config_deviceStatesOnWhichToSleep,
- states
+ states,
)
}
@@ -117,9 +117,10 @@ class FoldStateListenerTest : SysuiTestCase() {
}
companion object {
- private val DEVICE_STATE_FOLDED = Kosmos().foldedDeviceStateList.first()
- private val DEVICE_STATE_HALF_FOLDED = Kosmos().halfFoldedDeviceState
- private val DEVICE_STATE_UNFOLDED = Kosmos().unfoldedDeviceState
+ private val kosmos = Kosmos()
+ private val DEVICE_STATE_FOLDED = kosmos.foldedDeviceStateList.first()
+ private val DEVICE_STATE_HALF_FOLDED = kosmos.halfFoldedDeviceState
+ private val DEVICE_STATE_UNFOLDED = kosmos.unfoldedDeviceState
private const val FOLDED = true
private const val NOT_FOLDED = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt
index df45e2e21052..7946a68a6980 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt
@@ -22,6 +22,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.statusbar.policy.fake
import com.android.systemui.testKosmos
@@ -32,7 +33,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class BatteryInteractorTest : SysuiTestCase() {
- val kosmos = testKosmos()
+ val kosmos = testKosmos().useUnconfinedTestDispatcher()
val Kosmos.underTest by Kosmos.Fixture { batteryInteractor }
@Test
@@ -50,11 +51,61 @@ class BatteryInteractorTest : SysuiTestCase() {
assertThat(latest).isEqualTo(BatteryAttributionModel.Defend)
+ batteryController.fake._isDefender = false
+ batteryController.fake._isPowerSave = true
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.PowerSave)
+
+ batteryController.fake._isPowerSave = false
+ batteryController.fake._isPluggedIn = true
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.Charging)
+ }
+
+ @Test
+ fun attributionType_prioritizesPowerSaveOverCharging() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = true
+ batteryController.fake._isPowerSave = true
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.PowerSave)
+ }
+
+ @Test
+ fun attributionType_prioritizesPowerSaveOverDefender() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
batteryController.fake._isPowerSave = true
+ batteryController.fake._isDefender = false
assertThat(latest).isEqualTo(BatteryAttributionModel.PowerSave)
+ }
+
+ @Test
+ fun attributionType_prioritizesDefenderOverCharging() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
batteryController.fake._isPluggedIn = true
+ batteryController.fake._isPowerSave = false
+ batteryController.fake._isDefender = true
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.Defend)
+ }
+
+ @Test
+ fun attributionType_prioritizesChargingOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = false
+ batteryController.fake._isPowerSave = false
assertThat(latest).isEqualTo(BatteryAttributionModel.Charging)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt
index 6f4c745e8e7e..d8173486c8a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt
@@ -97,4 +97,39 @@ class BatteryViewModelTest : SysuiTestCase() {
assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.BoltLarge))
}
+
+ @Test
+ fun glyphList_attributionOrdering_prioritizesDefendOverCharging() =
+ kosmos.runTest {
+ fakeSystemSettingsRepository.setInt(Settings.System.SHOW_BATTERY_PERCENT, 0)
+ batteryController.fake._level = 39
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = true
+
+ assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.DefendLarge))
+ }
+
+ @Test
+ fun glyphList_attributionOrdering_prioritizesPowerSaveOverDefend() =
+ kosmos.runTest {
+ fakeSystemSettingsRepository.setInt(Settings.System.SHOW_BATTERY_PERCENT, 0)
+ batteryController.fake._level = 39
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = true
+ batteryController.fake._isPowerSave = true
+
+ assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.PlusLarge))
+ }
+
+ @Test
+ fun glyphList_attributionOrdering_prioritizesPowerSaveOverCharging() =
+ kosmos.runTest {
+ fakeSystemSettingsRepository.setInt(Settings.System.SHOW_BATTERY_PERCENT, 0)
+ batteryController.fake._level = 39
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = false
+ batteryController.fake._isPowerSave = true
+
+ assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.PlusLarge))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
index c22c62825d04..293e1fdd6d46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
@@ -57,7 +57,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyObject
+import org.mockito.ArgumentMatchers.any
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -138,7 +138,7 @@ class DeviceControlsControllerImplTest : SysuiTestCase() {
`when`(secureSettings.getInt(Settings.Secure.CONTROLS_ENABLED, 1)).thenReturn(0)
controller.setCallback(callback)
- verify(controlsListingController, never()).addCallback(anyObject())
+ verify(controlsListingController, never()).addCallback(any())
verify(callback).onControlsUpdate(null)
}
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 ffe7750dadfa..4a0445d5543a 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
@@ -269,14 +269,14 @@ public class RemoteInputViewTest extends SysuiTestCase {
when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
view.setViewRootImpl(viewRoot);
- /* verify that predictive back callback registered when RemoteInputView becomes visible */
- view.onVisibilityAggregated(true);
+ /* verify that predictive back callback registered when RemoteInputView gains focus */
+ view.focus();
verify(backInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
onBackInvokedCallbackCaptor.capture());
- /* verify that same callback unregistered when RemoteInputView becomes invisible */
- view.onVisibilityAggregated(false);
+ /* verify that same callback unregistered when RemoteInputView loses focus */
+ view.onDefocus(false, false, null);
verify(backInvokedDispatcher).unregisterOnBackInvokedCallback(
eq(onBackInvokedCallbackCaptor.getValue()));
}
@@ -299,13 +299,12 @@ public class RemoteInputViewTest extends SysuiTestCase {
view.onVisibilityAggregated(true);
view.setEditTextReferenceToSelf();
+ view.focus();
/* capture the callback during registration */
verify(backInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
onBackInvokedCallbackCaptor.capture());
- view.focus();
-
/* invoke the captured callback */
onBackInvokedCallbackCaptor.getValue().onBackInvoked();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index a553b176c34a..6618843ab46d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -26,9 +26,8 @@ import com.android.internal.util.LatencyTracker
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.foldedDeviceStateList
-import com.android.systemui.halfFoldedDeviceState
import com.android.systemui.keyguard.ScreenLifecycle
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.unfoldedDeviceState
@@ -70,12 +69,10 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(deviceStateManager.supportedDeviceStates).thenReturn(
- listOf(
- Kosmos().foldedDeviceStateList[0],
- Kosmos().unfoldedDeviceState
+ whenever(deviceStateManager.supportedDeviceStates)
+ .thenReturn(
+ listOf(testKosmos().foldedDeviceStateList[0], testKosmos().unfoldedDeviceState)
)
- )
unfoldLatencyTracker =
UnfoldLatencyTracker(
@@ -85,7 +82,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() {
context.mainExecutor,
context,
context.contentResolver,
- screenLifecycle
+ screenLifecycle,
)
.apply { init() }
@@ -206,7 +203,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() {
Settings.Global.putString(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
- durationScale.toString()
+ durationScale.toString(),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index 5f3442048fcd..422b20e8b951 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -45,7 +45,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -92,7 +92,7 @@ class GradientColorWallpaperTest : SysuiTestCase() {
engine.onSurfaceRedrawNeeded(surfaceHolder)
- verifyZeroInteractions(canvas)
+ verifyNoMoreInteractions(canvas)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 8281132e7502..0d7ce5353cd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -136,7 +136,6 @@ import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -166,7 +165,6 @@ import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvision
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.Flags;
@@ -206,8 +204,6 @@ import com.android.wm.shell.taskview.TaskViewRepository;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
-import kotlin.Lazy;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -218,9 +214,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -229,6 +222,10 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
+import kotlin.Lazy;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -451,7 +448,6 @@ public class BubblesTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mNotificationShadeWindowModel,
- new FakeSettings(),
mKosmos::getCommunalInteractor,
mKosmos.getShadeLayoutParams()
);
@@ -464,8 +460,8 @@ public class BubblesTest extends SysuiTestCase {
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
- mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
- mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiState = mKosmos.getSysuiState();
+ mSysUiState.addCallback((sysUiFlags, displayId) -> {
mSysUiStateBubblesManageMenuExpanded =
(sysUiFlags
& QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
@@ -605,14 +601,19 @@ public class BubblesTest extends SysuiTestCase {
// Get a reference to KeyguardStateController.Callback
verify(mKeyguardStateController, atLeastOnce())
.addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+
+ // Make sure mocks are set up for current user
+ switchUser(ActivityManager.getCurrentUser());
}
@After
public void tearDown() throws Exception {
- ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles());
- for (int i = 0; i < bubbles.size(); i++) {
- mBubbleController.removeBubble(bubbles.get(i).getKey(),
- Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ if (mBubbleData != null) {
+ ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles());
+ for (int i = 0; i < bubbles.size(); i++) {
+ mBubbleController.removeBubble(bubbles.get(i).getKey(),
+ Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ }
}
mTestableLooper.processAllMessages();
@@ -624,7 +625,8 @@ public class BubblesTest extends SysuiTestCase {
TAG,
String.format("waiting for animations to complete. attempt %d", retryCount));
// post a message to the looper and wait for it to be processed
- mTestableLooper.runWithLooper(() -> {});
+ mTestableLooper.runWithLooper(() -> {
+ });
retryCount++;
}
mTestableLooper.processAllMessages();
@@ -2053,6 +2055,9 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testShowStackEdu_isConversationBubble() {
+ // TODO(b/401025577): Prevent this test from raising a WTF, and remove this exemption
+ mLogWtfRule.addFailureLogExemption(log-> log.getTag().equals("FloatingCoordinator"));
+
// Setup
setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry();
@@ -2996,9 +3001,11 @@ public class BubblesTest extends SysuiTestCase {
}
@Override
- public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {}
+ public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {
+ }
@Override
- public void onItemDraggedOutsideBubbleBarDropZone() {}
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 252c70a61b86..e550e88b7bc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -46,6 +46,7 @@ import androidx.test.uiautomator.UiDevice;
import com.android.internal.protolog.ProtoLog;
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
import com.android.systemui.flags.SceneContainerRule;
+import com.android.systemui.log.LogWtfHandlerRule;
import org.junit.After;
import org.junit.AfterClass;
@@ -127,6 +128,8 @@ public abstract class SysuiTestCase {
@Rule public final SetFlagsRule mSetFlagsRule =
isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule();
+ @Rule public final LogWtfHandlerRule mLogWtfRule = new LogWtfHandlerRule();
+
@Rule(order = 10)
public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 0ba7c8574d85..a3d01355ac4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -47,6 +47,7 @@ import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.log.dagger.SceneFrameworkLog
+import com.android.systemui.media.NotificationMediaManager
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.model.SysUiState
import com.android.systemui.plugins.ActivityStarter
@@ -57,7 +58,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt
new file mode 100644
index 000000000000..130c2987912e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.carProjectionRepository by
+ Kosmos.Fixture<CarProjectionRepository> { FakeCarProjectionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt
new file mode 100644
index 000000000000..4042342923ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCarProjectionRepository : CarProjectionRepository {
+ private val _projectionActive = MutableStateFlow(false)
+ override val projectionActive: Flow<Boolean> = _projectionActive.asStateFlow()
+
+ override suspend fun isProjectionActive(): Boolean {
+ return _projectionActive.value
+ }
+
+ fun setProjectionActive(active: Boolean) {
+ _projectionActive.value = active
+ }
+}
+
+val CarProjectionRepository.fake
+ get() = this as FakeCarProjectionRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt
new file mode 100644
index 000000000000..23bbe36203e6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.carProjectionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.carProjectionInteractor by Fixture { CarProjectionInteractor(carProjectionRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt
new file mode 100644
index 000000000000..5735cf82cca0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.domain.interactor
+
+import com.android.systemui.common.domain.interactor.batteryInteractor
+import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
+import com.android.systemui.dock.dockManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundCoroutineContext
+
+val Kosmos.communalAutoOpenInteractor by Fixture {
+ CommunalAutoOpenInteractor(
+ communalSettingsInteractor = communalSettingsInteractor,
+ backgroundContext = backgroundCoroutineContext,
+ batteryInteractor = batteryInteractor,
+ posturingInteractor = posturingInteractor,
+ dockManager = dockManager,
+ allowSwipeAlways = false,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index b8b2ec5a58ae..316fcbb85b26 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,17 +16,14 @@
package com.android.systemui.communal.domain.interactor
-import android.content.pm.UserInfo
import android.content.testableContext
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.common.domain.interactor.batteryInteractor
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalSmartspaceRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
-import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.dock.dockManager
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -39,13 +36,9 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.phone.fakeManagedProfileController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.user.domain.interactor.userLockedInteractor
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
@@ -70,10 +63,6 @@ val Kosmos.communalInteractor by Fixture {
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
managedProfileController = fakeManagedProfileController,
- batteryInteractor = batteryInteractor,
- dockManager = dockManager,
- posturingInteractor = posturingInteractor,
- userLockedInteractor = userLockedInteractor,
)
}
@@ -86,28 +75,28 @@ fun Kosmos.setCommunalV2ConfigEnabled(enabled: Boolean) {
)
}
-suspend fun Kosmos.setCommunalEnabled(enabled: Boolean): UserInfo {
+fun Kosmos.setCommunalEnabled(enabled: Boolean) {
fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
- return if (enabled) {
- fakeUserRepository.asMainUser()
- } else {
- fakeUserRepository.asDefaultUser()
- }
+ val suppressionReasons =
+ if (enabled) {
+ emptyList()
+ } else {
+ listOf(SuppressionReason.ReasonUnknown())
+ }
+ communalSettingsInteractor.setSuppressionReasons(suppressionReasons)
}
-suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean): UserInfo {
+fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
setCommunalV2ConfigEnabled(enabled)
return setCommunalEnabled(enabled)
}
-suspend fun Kosmos.setCommunalAvailable(available: Boolean): UserInfo {
- val user = setCommunalEnabled(available)
+fun Kosmos.setCommunalAvailable(available: Boolean) {
+ setCommunalEnabled(available)
fakeKeyguardRepository.setKeyguardShowing(available)
- fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, available)
- return user
}
-suspend fun Kosmos.setCommunalV2Available(available: Boolean): UserInfo {
+fun Kosmos.setCommunalV2Available(available: Boolean) {
setCommunalV2ConfigEnabled(available)
- return setCommunalAvailable(available)
+ setCommunalAvailable(available)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
index fb983f7c605f..d2fbb515e686 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -24,7 +24,6 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.settings.userTracker
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
val Kosmos.communalSettingsInteractor by Fixture {
CommunalSettingsInteractor(
@@ -34,6 +33,5 @@ val Kosmos.communalSettingsInteractor by Fixture {
repository = communalSettingsRepository,
userInteractor = selectedUserInteractor,
userTracker = userTracker,
- tableLogBuffer = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
index aa4dd18a6cba..74d53b088c9b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
@@ -20,6 +20,7 @@ import android.content.clipboardManager
import android.content.res.mainResources
import com.android.systemui.development.data.repository.developmentSettingRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.user.data.repository.userRepository
@@ -31,5 +32,6 @@ val Kosmos.buildNumberInteractor by
userRepository,
{ clipboardManager },
testDispatcher,
+ applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index 6f570a86b19e..cd4b09c5267a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -21,7 +21,6 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.util.time.systemClock
@@ -31,8 +30,6 @@ val Kosmos.deviceEntryHapticsInteractor by
DeviceEntryHapticsInteractor(
biometricSettingsRepository = biometricSettingsRepository,
deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
- deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
- keyguardBypassInteractor = keyguardBypassInteractor,
deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
fingerprintPropertyRepository = fingerprintPropertyRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index d6f0e06e104d..338e4bec7aa2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import com.android.app.displaylib.DisplayRepository.PendingDisplay
import org.mockito.Mockito.`when` as whenever
/** Creates a mock display. */
@@ -41,8 +42,8 @@ fun display(type: Int, flags: Int = 0, id: Int = 0, state: Int? = null): Display
}
/** Creates a mock [DisplayRepository.PendingDisplay]. */
-fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
- mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
+fun createPendingDisplay(id: Int = 0): PendingDisplay =
+ mock<PendingDisplay> { whenever(this.id).thenReturn(id) }
@SysUISingleton
/** Fake [DisplayRepository] implementation for testing. */
@@ -50,9 +51,10 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val flow = MutableStateFlow<Set<Display>>(emptySet())
private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet())
private val pendingDisplayFlow =
- MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
+ MutableSharedFlow<PendingDisplay?>(replay = 1)
private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
+ private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet())
suspend fun addDisplay(displayId: Int, type: Int = Display.TYPE_EXTERNAL) {
addDisplay(display(type, id = displayId))
@@ -62,10 +64,19 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
displays.forEach { addDisplay(it) }
}
+ suspend operator fun plusAssign(display: Display) {
+ addDisplay(display)
+ }
+
+ suspend operator fun minusAssign(displayId: Int) {
+ removeDisplay(displayId)
+ }
+
suspend fun addDisplay(display: Display) {
flow.value += display
displayIdFlow.value += display.displayId
displayAdditionEventFlow.emit(display)
+ displayIdsWithSystemDecorationsFlow.value += display.displayId
}
suspend fun removeDisplay(displayId: Int) {
@@ -74,6 +85,16 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
displayRemovalEventFlow.emit(displayId)
}
+ suspend fun triggerAddDisplaySystemDecorationEvent(displayId: Int) {
+ displayIdsWithSystemDecorationsFlow.value += displayId
+ displayIdsWithSystemDecorationsFlow.emit(displayIdsWithSystemDecorationsFlow.value)
+ }
+
+ suspend fun triggerRemoveSystemDecorationEvent(displayId: Int) {
+ displayIdsWithSystemDecorationsFlow.value -= displayId
+ displayIdsWithSystemDecorationsFlow.emit(displayIdsWithSystemDecorationsFlow.value)
+ }
+
/** Emits [value] as [displayAdditionEvent] flow value. */
suspend fun emit(value: Display?) = displayAdditionEventFlow.emit(value)
@@ -81,7 +102,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
suspend fun emit(value: Set<Display>) = flow.emit(value)
/** Emits [value] as [pendingDisplay] flow value. */
- suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value)
+ suspend fun emit(value: PendingDisplay?) = pendingDisplayFlow.emit(value)
override val displays: StateFlow<Set<Display>>
get() = flow
@@ -89,7 +110,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
override val displayIds: StateFlow<Set<Int>>
get() = displayIdFlow
- override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
+ override val pendingDisplay: Flow<PendingDisplay?>
get() = pendingDisplayFlow
private val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -104,7 +125,8 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
- override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = MutableStateFlow(emptySet())
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+ displayIdsWithSystemDecorationsFlow
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
index e3797260ed6d..5ab3b3de49f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -16,8 +16,10 @@
package com.android.systemui.display.data.repository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import kotlinx.coroutines.CoroutineScope
class FakePerDisplayStore(
@@ -47,3 +49,31 @@ val Kosmos.fakePerDisplayStore by
displayRepository = displayRepository,
)
}
+
+class FakePerDisplayInstanceProviderWithTeardown :
+ PerDisplayInstanceProviderWithTeardown<TestPerDisplayInstance> {
+ val destroyed = mutableListOf<TestPerDisplayInstance>()
+
+ override fun destroyInstance(instance: TestPerDisplayInstance) {
+ destroyed += instance
+ }
+
+ override fun createInstance(displayId: Int): TestPerDisplayInstance? {
+ return TestPerDisplayInstance(displayId)
+ }
+}
+
+val Kosmos.fakePerDisplayInstanceProviderWithTeardown by
+ Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() }
+
+val Kosmos.perDisplayDumpHelper by Kosmos.Fixture { PerDisplayRepoDumpHelper(dumpManager) }
+val Kosmos.fakePerDisplayInstanceRepository by
+ Kosmos.Fixture {
+ PerDisplayInstanceRepositoryImpl(
+ debugName = "fakePerDisplayInstanceRepository",
+ instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
+ testScope.backgroundScope,
+ displayRepository,
+ perDisplayDumpHelper,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
deleted file mode 100644
index b99310bcbe38..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
+++ /dev/null
@@ -1,66 +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.dock;
-
-/**
- * A rudimentary fake for DockManager.
- */
-public class DockManagerFake implements DockManager {
- DockEventListener mCallback;
- AlignmentStateListener mAlignmentListener;
- private boolean mDocked;
-
- @Override
- public void addListener(DockEventListener callback) {
- this.mCallback = callback;
- }
-
- @Override
- public void removeListener(DockEventListener callback) {
- this.mCallback = null;
- }
-
- @Override
- public void addAlignmentStateListener(AlignmentStateListener listener) {
- mAlignmentListener = listener;
- }
-
- @Override
- public void removeAlignmentStateListener(AlignmentStateListener listener) {
- mAlignmentListener = listener;
- }
-
- @Override
- public boolean isDocked() {
- return mDocked;
- }
-
- /** Sets the docked state */
- public void setIsDocked(boolean docked) {
- mDocked = docked;
- }
-
- @Override
- public boolean isHidden() {
- return false;
- }
-
- /** Notifies callbacks of dock state change */
- public void setDockEvent(int event) {
- mCallback.onEvent(event);
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt
new file mode 100644
index 000000000000..6a43c40612a7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dock
+
+import com.android.systemui.dock.DockManager.AlignmentStateListener
+
+/** A rudimentary fake for DockManager. */
+class DockManagerFake : DockManager {
+ private val callbacks = mutableSetOf<DockManager.DockEventListener>()
+ private val alignmentListeners = mutableSetOf<AlignmentStateListener>()
+ private var docked = false
+
+ override fun addListener(callback: DockManager.DockEventListener) {
+ callbacks.add(callback)
+ }
+
+ override fun removeListener(callback: DockManager.DockEventListener) {
+ callbacks.remove(callback)
+ }
+
+ override fun addAlignmentStateListener(listener: AlignmentStateListener) {
+ alignmentListeners.add(listener)
+ }
+
+ override fun removeAlignmentStateListener(listener: AlignmentStateListener) {
+ alignmentListeners.remove(listener)
+ }
+
+ override fun isDocked(): Boolean {
+ return docked
+ }
+
+ /** Sets the docked state */
+ fun setIsDocked(docked: Boolean) {
+ this.docked = docked
+ }
+
+ override fun isHidden(): Boolean {
+ return false
+ }
+
+ /** Notifies callbacks of dock state change */
+ fun setDockEvent(event: Int) {
+ for (callback in callbacks) {
+ callback.onEvent(event)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
index 97dab4987f6c..c9e25c31faa0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
@@ -27,9 +27,16 @@ class FakeKeyEventRepository @Inject constructor() : KeyEventRepository {
private val _isPowerButtonDown = MutableStateFlow(false)
override val isPowerButtonDown: Flow<Boolean> = _isPowerButtonDown.asStateFlow()
+ private val _isPowerButtonLongPressed = MutableStateFlow(false)
+ override val isPowerButtonLongPressed = _isPowerButtonLongPressed.asStateFlow()
+
fun setPowerButtonDown(isDown: Boolean) {
_isPowerButtonDown.value = isDown
}
+
+ fun setPowerButtonLongPressed(isLongPressed: Boolean) {
+ _isPowerButtonLongPressed.value = isLongPressed
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt
new file mode 100644
index 000000000000..6fd7ef315fcc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.keyevent.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.mockSysUIKeyEventHandler by Kosmos.Fixture { mock<SysUIKeyEventHandler>() }
+var Kosmos.sysUIKeyEventHandler by Kosmos.Fixture { mockSysUIKeyEventHandler }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt
new file mode 100644
index 000000000000..361d21dc134d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardServiceShowLockscreenRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardServiceShowLockscreenRepository by
+ Kosmos.Fixture { KeyguardServiceShowLockscreenRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index bdfa875f5429..9b0a9830dcc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
@@ -43,6 +42,5 @@ val Kosmos.fromAodTransitionInteractor by
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
communalSettingsInteractor = communalSettingsInteractor,
communalSceneInteractor = communalSceneInteractor,
- communalInteractor = communalInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 985044c80f18..511bede7349b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -42,7 +41,6 @@ var Kosmos.fromLockscreenTransitionInteractor by
communalSettingsInteractor = communalSettingsInteractor,
swipeToDismissInteractor = swipeToDismissInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
- communalInteractor = communalInteractor,
communalSceneInteractor = communalSceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 255a780a84be..113059222aa2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.applicationContext
+import android.os.powerManager
import android.view.accessibility.accessibilityManagerWrapper
import com.android.internal.logging.uiEventLogger
import com.android.systemui.broadcast.broadcastDispatcher
@@ -26,6 +27,8 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.pulsingGestureListener
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
+import com.android.systemui.util.time.fakeSystemClock
val Kosmos.keyguardTouchHandlingInteractor by
Kosmos.Fixture {
@@ -40,5 +43,8 @@ val Kosmos.keyguardTouchHandlingInteractor by
accessibilityManager = accessibilityManagerWrapper,
pulsingGestureListener = pulsingGestureListener,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ secureSettingsRepository = userAwareSecureSettingsRepository,
+ powerManager = powerManager,
+ systemClock = fakeSystemClock,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt
new file mode 100644
index 000000000000..447aa1255463
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.keyguard.data.repository.keyguardServiceShowLockscreenRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.userTracker
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.keyguardServiceShowLockscreenInteractor by
+ Kosmos.Fixture {
+ KeyguardServiceShowLockscreenInteractor(
+ backgroundScope = testScope,
+ selectedUserInteractor = selectedUserInteractor,
+ repository = keyguardServiceShowLockscreenRepository,
+ userTracker = userTracker,
+ wmLockscreenVisibilityInteractor = { windowManagerLockscreenVisibilityInteractor },
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt
index f7caeb69f17e..dd868e767d9b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardShowWhileAwakeInteractorKosmos.kt
@@ -18,10 +18,12 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
val Kosmos.keyguardShowWhileAwakeInteractor by
Kosmos.Fixture {
KeyguardShowWhileAwakeInteractor(
+ backgroundScope = testScope,
biometricSettingsRepository = biometricSettingsRepository,
keyguardEnabledInteractor = keyguardEnabledInteractor,
keyguardServiceShowLockscreenInteractor = keyguardServiceShowLockscreenInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
index 43fa718cff9a..c89fb705f417 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -27,7 +27,7 @@ import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.systemClock
-val Kosmos.keyguardWakeDirectlyToGoneInteractor by
+val Kosmos.keyguardWakeDirectlyToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor by
Kosmos.Fixture {
KeyguardWakeDirectlyToGoneInteractor(
applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
index 697e7b9476ca..3f3c3c0d478a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.binder
import android.content.applicationContext
import android.view.mockedLayoutInflater
import android.view.windowManager
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
@@ -37,6 +38,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
val Kosmos.alternateBouncerViewBinder by
@@ -76,5 +78,7 @@ private val Kosmos.alternateBouncerUdfpsIconViewModel by
fingerprintPropertyInteractor = fingerprintPropertyInteractor,
udfpsOverlayInteractor = udfpsOverlayInteractor,
alternateBouncerViewModel = alternateBouncerViewModel,
+ statusBarKeyguardViewManager = statusBarKeyguardViewManager,
+ accessibilityInteractor = accessibilityInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
index 2797b4409ff0..bf456978d983 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt
@@ -48,5 +48,6 @@ val Kosmos.deviceEntryBackgroundViewModel by Fixture {
primaryBouncerToLockscreenTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
glanceableHubToAodTransitionViewModel = glanceableHubToAodTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 27ca0f867855..a9aa8cd5a7f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -84,6 +84,7 @@ val Kosmos.keyguardRootViewModel by Fixture {
occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ occludedToPrimaryBouncerTransitionViewModel = occludedToPrimaryBouncerTransitionViewModel,
offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel,
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
index 11f0c19ffa67..7093a941485a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
@@ -115,14 +115,14 @@ private constructor(
fun assertThat(
repository: KeyguardTransitionRepository
): KeyguardTransitionRepositorySpySubject =
- assertAbout { failureMetadata, repository: KeyguardTransitionRepository ->
+ assertAbout { failureMetadata, repository: KeyguardTransitionRepository? ->
if (!Mockito.mockingDetails(repository).isSpy) {
fail(
"Cannot assert on a non-spy KeyguardTransitionRepository. " +
"Use Mockito.spy(keyguardTransitionRepository)."
)
}
- KeyguardTransitionRepositorySpySubject(failureMetadata, repository)
+ KeyguardTransitionRepositorySpySubject(failureMetadata, repository!!)
}
.that(repository)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index dff9f3abfc05..623989ec5809 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -53,6 +53,8 @@ import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.model.sceneContainerPlugin
+import com.android.systemui.model.sysUIStateDispatcher
+import com.android.systemui.model.sysUiState
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -64,6 +66,8 @@ import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.scene.ui.view.mockWindowRootViewProvider
import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+import com.android.systemui.settings.displayTracker
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeLayoutParams
@@ -198,4 +202,8 @@ class KosmosJavaAdapter() {
val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor }
+ val sysuiState by lazy { kosmos.sysUiState }
+ val displayTracker by lazy { kosmos.displayTracker }
+ val fakeShadeDisplaysRepository by lazy { kosmos.fakeShadeDisplaysRepository }
+ val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index 5e67182d7353..a42f2025cdaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -13,89 +13,86 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.systemui.log
import android.util.Log
import android.util.Log.TerribleFailureHandler
-import junit.framework.Assert
+import com.google.common.truth.Truth.assertWithMessage
-/** Asserts that the given block does not make a call to Log.wtf */
-fun assertDoesNotLogWtf(
+/** Asserts that [notLoggingBlock] does not make a call to [Log.wtf] */
+fun <T> assertDoesNotLogWtf(
message: String = "Expected Log.wtf not to be called",
- notLoggingBlock: () -> Unit,
-) {
+ notLoggingBlock: () -> T,
+): T {
var caught: TerribleFailureLog? = null
val newHandler = TerribleFailureHandler { tag, failure, system ->
caught = TerribleFailureLog(tag, failure, system)
}
val oldHandler = Log.setWtfHandler(newHandler)
- try {
- notLoggingBlock()
- } finally {
- Log.setWtfHandler(oldHandler)
- }
+ val result =
+ try {
+ notLoggingBlock()
+ } finally {
+ Log.setWtfHandler(oldHandler)
+ }
caught?.let { throw AssertionError("$message: $it", it.failure) }
+ return result
}
-fun assertDoesNotLogWtf(
- message: String = "Expected Log.wtf not to be called",
- notLoggingRunnable: Runnable,
-) = assertDoesNotLogWtf(message = message) { notLoggingRunnable.run() }
-
-/**
- * Assert that the given block makes a call to Log.wtf
- *
- * @return the details of the log
- */
-fun assertLogsWtf(
+/** Assert that [loggingBlock] makes a call to [Log.wtf] */
+@JvmOverloads
+fun <T> assertLogsWtf(
message: String = "Expected Log.wtf to be called",
allowMultiple: Boolean = false,
- loggingBlock: () -> Unit,
-): TerribleFailureLog {
- var caught: TerribleFailureLog? = null
- var count = 0
+ loggingBlock: () -> T,
+): WtfBlockResult<T> {
+ val caught = mutableListOf<TerribleFailureLog>()
val newHandler = TerribleFailureHandler { tag, failure, system ->
- if (caught == null) {
- caught = TerribleFailureLog(tag, failure, system)
- }
- count++
+ caught.add(TerribleFailureLog(tag, failure, system))
}
val oldHandler = Log.setWtfHandler(newHandler)
- try {
- loggingBlock()
- } finally {
- Log.setWtfHandler(oldHandler)
- }
- Assert.assertNotNull(message, caught)
- if (!allowMultiple && count != 1) {
- Assert.fail("Unexpectedly caught Log.Wtf $count times; expected only 1. First: $caught")
+ val result =
+ try {
+ loggingBlock()
+ } finally {
+ Log.setWtfHandler(oldHandler)
+ }
+ assertWithMessage(message).that(caught).isNotEmpty()
+ if (!allowMultiple) {
+ assertWithMessage("Unexpectedly caught Log.Wtf multiple times").that(caught).hasSize(1)
}
- return caught!!
+ return WtfBlockResult(caught, result)
}
+/** Assert that [loggingBlock] makes at least one call to [Log.wtf] */
+@JvmOverloads
+fun <T> assertLogsWtfs(
+ message: String = "Expected Log.wtf to be called once or more",
+ loggingBlock: () -> T,
+): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingBlock)
+
+/** The data passed to [TerribleFailureHandler.onTerribleFailure] */
+data class TerribleFailureLog(
+ val tag: String,
+ val failure: Log.TerribleFailure,
+ val system: Boolean,
+)
+
+/** The [Log.wtf] logs and return value of the block */
+data class WtfBlockResult<T>(val logs: List<TerribleFailureLog>, val result: T)
+
+/** Assert that [loggingRunnable] makes a call to [Log.wtf] */
@JvmOverloads
-fun assertLogsWtf(
+fun assertRunnableLogsWtf(
message: String = "Expected Log.wtf to be called",
allowMultiple: Boolean = false,
loggingRunnable: Runnable,
-): TerribleFailureLog =
+): WtfBlockResult<Unit> =
assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() }
-fun assertLogsWtfs(
- message: String = "Expected Log.wtf to be called once or more",
- loggingBlock: () -> Unit,
-): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock)
-
+/** Assert that [loggingRunnable] makes at least one call to [Log.wtf] */
@JvmOverloads
-fun assertLogsWtfs(
+fun assertRunnableLogsWtfs(
message: String = "Expected Log.wtf to be called once or more",
loggingRunnable: Runnable,
-): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() }
-
-/** The data passed to [TerribleFailureHandler.onTerribleFailure] */
-data class TerribleFailureLog(
- val tag: String,
- val failure: Log.TerribleFailure,
- val system: Boolean
-)
+): WtfBlockResult<Unit> = assertRunnableLogsWtf(message, allowMultiple = true, loggingRunnable)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt
index e639326bd7a1..0e348c88f058 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt
@@ -24,21 +24,23 @@ import org.junit.runners.model.Statement
class LogWtfHandlerRule : TestRule {
- private var started = false
- private var handler = ThrowAndFailAtEnd
+ private var failureLogExemptions = mutableListOf<FailureLogExemption>()
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
- started = true
+ val handler = TerribleFailureTestHandler()
val originalWtfHandler = Log.setWtfHandler(handler)
var failure: Throwable? = null
try {
base.evaluate()
} catch (ex: Throwable) {
- failure = ex.runAndAddSuppressed { handler.onTestFailure(ex) }
+ failure = ex
} finally {
- failure = failure.runAndAddSuppressed { handler.onTestFinished() }
+ failure =
+ runAndAddSuppressed(failure) {
+ handler.onTestFinished(failureLogExemptions)
+ }
Log.setWtfHandler(originalWtfHandler)
}
if (failure != null) {
@@ -48,74 +50,52 @@ class LogWtfHandlerRule : TestRule {
}
}
- fun Throwable?.runAndAddSuppressed(block: () -> Unit): Throwable? {
+ /** Adds a log failure exemption. Exemptions are evaluated at the end of the test. */
+ fun addFailureLogExemption(exemption: FailureLogExemption) {
+ failureLogExemptions.add(exemption)
+ }
+
+ /** Clears and sets exemptions. Exemptions are evaluated at the end of the test. */
+ fun resetFailureLogExemptions(vararg exemptions: FailureLogExemption) {
+ failureLogExemptions = exemptions.toMutableList()
+ }
+
+ private fun runAndAddSuppressed(currentError: Throwable?, block: () -> Unit): Throwable? {
try {
block()
} catch (t: Throwable) {
- if (this == null) {
+ if (currentError == null) {
return t
}
- addSuppressed(t)
+ currentError.addSuppressed(t)
}
- return this
+ return currentError
}
- fun setWtfHandler(handler: TerribleFailureTestHandler) {
- check(!started) { "Should only be called before the test starts" }
- this.handler = handler
- }
-
- fun interface TerribleFailureTestHandler : TerribleFailureHandler {
- fun onTestFailure(failure: Throwable) {}
- fun onTestFinished() {}
- }
-
- companion object Handlers {
- val ThrowAndFailAtEnd
- get() =
- object : TerribleFailureTestHandler {
- val failures = mutableListOf<Log.TerribleFailure>()
-
- override fun onTerribleFailure(
- tag: String,
- what: Log.TerribleFailure,
- system: Boolean
- ) {
- failures.add(what)
- throw what
- }
+ private class TerribleFailureTestHandler : TerribleFailureHandler {
+ private val failureLogs = mutableListOf<FailureLog>()
- override fun onTestFailure(failure: Throwable) {
- super.onTestFailure(failure)
- }
+ override fun onTerribleFailure(tag: String, what: Log.TerribleFailure, system: Boolean) {
+ failureLogs.add(FailureLog(tag = tag, failure = what, system = system))
+ }
- override fun onTestFinished() {
- if (failures.isNotEmpty()) {
- throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0])
- }
- }
+ fun onTestFinished(exemptions: List<FailureLogExemption>) {
+ val failures =
+ failureLogs.filter { failureLog ->
+ !exemptions.any { it.isFailureLogExempt(failureLog) }
}
+ if (failures.isNotEmpty()) {
+ throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0].failure)
+ }
+ }
+ }
- val JustThrow = TerribleFailureTestHandler { _, what, _ -> throw what }
-
- val JustFailAtEnd
- get() =
- object : TerribleFailureTestHandler {
- val failures = mutableListOf<Log.TerribleFailure>()
-
- override fun onTerribleFailure(
- tag: String,
- what: Log.TerribleFailure,
- system: Boolean
- ) {
- failures.add(what)
- }
+ /** All the information from a call to [Log.wtf] that was handed to [TerribleFailureHandler] */
+ data class FailureLog(val tag: String, val failure: Log.TerribleFailure, val system: Boolean)
- override fun onTestFinished() {
- if (failures.isNotEmpty()) {
- throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0])
- }
- }
- }
+ /** An interface for exempting a [FailureLog] from causing a test failure. */
+ fun interface FailureLogExemption {
+ /** Determines whether a log should be except from failing the test. */
+ fun isFailureLogExempt(log: FailureLog): Boolean
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
deleted file mode 100644
index 1edd405f4af6..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.domain.pipeline.interactor
-
-import android.content.applicationContext
-import com.android.systemui.broadcast.broadcastSender
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.media.controls.data.repository.mediaFilterRepository
-import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
-import com.android.systemui.plugins.activityStarter
-
-val Kosmos.mediaRecommendationsInteractor by
- Kosmos.Fixture {
- MediaRecommendationsInteractor(
- applicationScope = applicationCoroutineScope,
- applicationContext = applicationContext,
- repository = mediaFilterRepository,
- mediaDataProcessor = mediaDataProcessor,
- broadcastSender = broadcastSender,
- activityStarter = activityStarter,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
index 5e6434d84538..976b4046f58d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
@@ -37,7 +37,6 @@ val Kosmos.mediaCarouselViewModel by
visualStabilityProvider = visualStabilityProvider,
interactor = mediaCarouselInteractor,
controlInteractorFactory = mediaControlInteractorFactory,
- recommendationsViewModel = mediaRecommendationsViewModel,
logger = mediaUiEventLogger,
mediaLogger = mediaLogger,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt
deleted file mode 100644
index 34a527781979..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
-
-import android.content.applicationContext
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
-import com.android.systemui.media.controls.util.mediaUiEventLogger
-
-val Kosmos.mediaRecommendationsViewModel by
- Kosmos.Fixture {
- MediaRecommendationsViewModel(
- applicationContext = applicationContext,
- backgroundDispatcher = testDispatcher,
- interactor = mediaRecommendationsInteractor,
- logger = mediaUiEventLogger,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
index d19dfe8d74fb..79506f9e75a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
@@ -20,10 +20,12 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
val Kosmos.sceneContainerPlugin by Fixture {
SceneContainerPlugin(
sceneInteractor = { sceneInteractor },
occlusionInteractor = { sceneContainerOcclusionInteractor },
+ shadeDisplaysRepository = { fakeShadeDisplaysRepository },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
index 6ddf633e58e5..11bd4c7b7940 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
@@ -16,16 +16,42 @@
package com.android.systemui.model
+import android.view.Display
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor
+import com.android.systemui.display.data.repository.FakePerDisplayRepository
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.settings.displayTracker
import org.mockito.Mockito.spy
-val Kosmos.sysUiState by Fixture {
- spy(
- SysUiState(
- displayTracker,
+val Kosmos.sysUiState by Fixture { sysUiStateFactory.create(Display.DEFAULT_DISPLAY) }
+val Kosmos.sysUIStateDispatcher by Fixture { SysUIStateDispatcher() }
+
+val Kosmos.sysUiStateFactory by Fixture {
+ object : SysUiStateImpl.Factory {
+ override fun create(displayId: Int): SysUiStateImpl {
+ return spy(
+ SysUiStateImpl(displayId, sceneContainerPlugin, dumpManager, sysUIStateDispatcher)
+ )
+ }
+ }
+}
+
+val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { FakePerDisplayRepository<SysUiState>() }
+
+val Kosmos.sysuiStateInteractor by Fixture {
+ SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository, displayRepository)
+}
+
+val Kosmos.sysUiStateOverrideFactory by Fixture {
+ { displayId: Int ->
+ SysUIStateOverride(
+ displayId,
sceneContainerPlugin,
+ dumpManager,
+ sysUiState,
+ sysUIStateDispatcher,
)
- )
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
index 4f8d5a14e390..9457de18b3b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
@@ -18,18 +18,14 @@ package com.android.systemui.qs
import com.android.systemui.plugins.qs.TileDetailsViewModel
-class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel() {
+class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel {
private var _clickOnSettingsButton = 0
override fun clickOnSettingsButton() {
_clickOnSettingsButton++
}
- override fun getTitle(): String {
- return tileSpec ?: " Fake title"
- }
+ override val title = tileSpec ?: " Fake title"
- override fun getSubTitle(): String {
- return tileSpec ?: "Fake sub title"
- }
+ override val subTitle = tileSpec ?: "Fake sub title"
}
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 43307701b6fb..4618dc78dcc6 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
@@ -58,7 +58,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.settings.GlobalSettings
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -94,7 +94,7 @@ class FooterActionsTestUtils(
return createFooterActionsViewModel(
context,
footerActionsInteractor,
- flowOf(shadeMode),
+ MutableStateFlow(shadeMode),
falsingManager,
globalActionsDialogLite,
mockActivityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt
index 75ca311689ce..4aa4a2b1a73a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.panels.ui.viewmodel.toolbar
import android.content.applicationContext
import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory
import com.android.systemui.globalactions.globalActionsDialogLite
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.footerActionsInteractor
@@ -29,6 +30,7 @@ val Kosmos.toolbarViewModelFactory by
override fun create(): ToolbarViewModel {
return ToolbarViewModel(
editModeButtonViewModelFactory,
+ buildNumberViewModelFactory,
footerActionsInteractor,
{ globalActionsDialogLite },
falsingInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt
index e09f2806ce5c..c1e689c0165c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt
@@ -68,8 +68,8 @@ private constructor(
): QSTileIntentUserInputHandlerSubject =
Truth.assertAbout {
failureMetadata: FailureMetadata,
- subject: FakeQSTileIntentUserInputHandler ->
- QSTileIntentUserInputHandlerSubject(failureMetadata, subject)
+ subject: FakeQSTileIntentUserInputHandler? ->
+ QSTileIntentUserInputHandlerSubject(failureMetadata, subject!!)
}
.that(handler)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index aa29808bd9ee..657a95a3261e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -63,7 +63,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) :
companion object {
/** Returns a factory to be used with [Truth.assertAbout]. */
- fun states(): Factory<QSTileStateSubject, QSTileState?> {
+ fun states(): Factory<QSTileStateSubject, QSTileState> {
return Factory { failureMetadata: FailureMetadata, subject: QSTileState? ->
QSTileStateSubject(failureMetadata, subject)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
index d2351dc8ae18..cc8b44b0dade 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
@@ -59,7 +59,7 @@ class TileSubject private constructor(failureMetadata: FailureMetadata, subject:
companion object {
/** Returns a factory to be used with [Truth.assertAbout]. */
- fun tiles(): Factory<TileSubject, Tile?> {
+ fun tiles(): Factory<TileSubject, Tile> {
return Factory { failureMetadata: FailureMetadata, subject: Tile? ->
TileSubject(failureMetadata, subject)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt
new file mode 100644
index 000000000000..9369057240f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scene.ui.view
+
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.keyevent.domain.interactor.sysUIKeyEventHandler
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.windowRootViewKeyEventHandler by
+ Kosmos.Fixture {
+ WindowRootViewKeyEventHandler(
+ sysUIKeyEventHandlerLazy = { sysUIKeyEventHandler },
+ falsingCollector = falsingCollector,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 9776fd91134d..66a17514c106 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.core
import android.content.testableContext
-import android.view.mockIWindowManager
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayScopeRepository
@@ -94,6 +93,5 @@ val Kosmos.multiDisplayStatusBarStarter by
statusBarInitializerStore,
privacyDotWindowControllerStore,
lightBarControllerStore,
- mockIWindowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt
new file mode 100644
index 000000000000..0db8ee8a9109
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.CoroutineScope
+
+class FakeStatusBarPerDisplayStore(
+ backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+) :
+ StatusBarPerDisplayStoreImpl<TestPerDisplayInstance>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ val removalActions = mutableListOf<TestPerDisplayInstance>()
+
+ override fun createInstanceForDisplay(displayId: Int): TestPerDisplayInstance {
+ return TestPerDisplayInstance(displayId)
+ }
+
+ override val instanceClass = TestPerDisplayInstance::class.java
+
+ override suspend fun onDisplayRemovalAction(instance: TestPerDisplayInstance) {
+ removalActions += instance
+ }
+}
+
+data class TestPerDisplayInstance(val displayId: Int)
+
+val Kosmos.fakeStatusBarPerDisplayStore by
+ Kosmos.Fixture {
+ FakeStatusBarPerDisplayStore(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
index 7145907a14a8..39391d03a44b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
@@ -18,14 +18,19 @@ package com.android.systemui.statusbar.featurepods.media.ui.viewmodel
import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.featurepods.media.domain.interactor.mediaControlChipInteractor
-val Kosmos.mediaControlChipViewModel: MediaControlChipViewModel by
+private val Kosmos.mediaControlChipViewModel: MediaControlChipViewModel by
Kosmos.Fixture {
MediaControlChipViewModel(
- backgroundScope = applicationCoroutineScope,
applicationContext = testableContext,
mediaControlChipInteractor = mediaControlChipInteractor,
)
}
+
+val Kosmos.mediaControlChipViewModelFactory by
+ Kosmos.Fixture {
+ object : MediaControlChipViewModel.Factory {
+ override fun create(): MediaControlChipViewModel = mediaControlChipViewModel
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
index b876095fefe5..2a3167cb66f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.mediaControlChipViewModel
+import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.mediaControlChipViewModelFactory
private val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by
- Kosmos.Fixture { StatusBarPopupChipsViewModel(mediaControlChip = mediaControlChipViewModel) }
+ Kosmos.Fixture {
+ StatusBarPopupChipsViewModel(mediaControlChipFactory = mediaControlChipViewModelFactory)
+ }
val Kosmos.statusBarPopupChipsViewModelFactory by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
index c337ac201b3d..8e98fe31a56a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
@@ -18,6 +18,10 @@ package com.android.systemui.statusbar.notification
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.statusBarNotificationActivityStarter
+import org.mockito.kotlin.mock
var Kosmos.notificationActivityStarter: NotificationActivityStarter by
Kosmos.Fixture { statusBarNotificationActivityStarter }
+
+var Kosmos.mockNotificationActivityStarter: NotificationActivityStarter by
+Kosmos.Fixture { mock<NotificationActivityStarter>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt
new file mode 100644
index 000000000000..f3cdabb5813e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.render
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.groupMembershipManager by Kosmos.Fixture { mock<GroupMembershipManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt
new file mode 100644
index 000000000000..c263c5d96610
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.notificationPersonExtractor by Kosmos.Fixture { mock<NotificationPersonExtractor>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt
new file mode 100644
index 000000000000..20982eb43797
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifierKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.render.groupMembershipManager
+
+val Kosmos.peopleNotificationIdentifier by
+ Kosmos.Fixture {
+ PeopleNotificationIdentifierImpl(notificationPersonExtractor, groupMembershipManager)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index da879d9e314d..2b3158da38f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -39,7 +39,8 @@ fun Kosmos.setPromotedContent(entry: NotificationEntry) {
promotedNotificationContentExtractor.extractContent(
entry,
Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification),
- RowImageInflater.newInstance(null).useForContentModel(),
+ RowImageInflater.newInstance(previousIndex = null, reinflating = false)
+ .useForContentModel(),
)
entry.promotedNotificationContentModel =
requireNotNull(extractedContent) { "extractContent returned null" }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt
new file mode 100644
index 000000000000..38b59945ab7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PackageDemotionInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.promoted.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.packageDemotionInteractor by Kosmos.Fixture { PackageDemotionInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt
new file mode 100644
index 000000000000..067e420b89c3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.row
+
+import com.android.internal.logging.metricsLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl
+import com.android.systemui.statusbar.notification.collection.coordinator.visualStabilityCoordinator
+import com.android.systemui.statusbar.notification.mockNotificationActivityStarter
+import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider
+
+val Kosmos.entryAdapterFactory by
+ Kosmos.Fixture {
+ EntryAdapterFactoryImpl(
+ mockNotificationActivityStarter,
+ metricsLogger,
+ peopleNotificationIdentifier,
+ notificationIconStyleProvider,
+ visualStabilityCoordinator,
+ mockNotificationActionClickManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 771e1a5dccc3..6a674ca29ca4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -37,6 +37,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.media.NotificationMediaManager
import com.android.systemui.media.controls.util.MediaFeatureFlag
import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.plugins.ActivityStarter
@@ -45,7 +46,6 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.DevicePolicyManagerWrapper
import com.android.systemui.shared.system.PackageManagerWrapper
-import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.RankingBuilder
@@ -53,8 +53,11 @@ import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.ColorUpdateLogger
import com.android.systemui.statusbar.notification.ConversationNotificationManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactoryImpl
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
@@ -76,6 +79,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import com.android.systemui.statusbar.notification.row.icon.AppIconProviderImpl
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProviderImpl
import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
@@ -364,11 +368,23 @@ class ExpandableNotificationRowBuilder(
)
val row = rowInflaterTask.inflateSynchronously(context, null, entry)
+ val entryAdapter =
+ EntryAdapterFactoryImpl(
+ Mockito.mock(NotificationActivityStarter::class.java),
+ Mockito.mock(MetricsLogger::class.java),
+ Mockito.mock(PeopleNotificationIdentifier::class.java),
+ Mockito.mock(NotificationIconStyleProvider::class.java),
+ Mockito.mock(VisualStabilityCoordinator::class.java),
+ Mockito.mock(NotificationActionClickManager::class.java),
+ )
+ .create(entry)
+
entry.row = row
mIconManager.createIcons(entry)
mBindPipelineEntryListener.onEntryInit(entry)
mBindPipeline.manageRow(entry, row)
row.initialize(
+ entryAdapter,
entry,
Mockito.mock(RemoteInputViewSubcomponent.Factory::class.java, STUB_ONLY),
APP_NAME,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt
new file mode 100644
index 000000000000..8e62ae8825f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.row
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.mockNotificationActionClickManager: NotificationActionClickManager by
+ Kosmos.Fixture { mock<NotificationActionClickManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index fbc2a21b0888..219ecbfe963b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
@@ -35,6 +36,7 @@ val Kosmos.notificationListViewModel by Fixture {
NotificationListViewModel(
notificationShelfViewModel,
hideListViewModel,
+ ongoingActivityChipsViewModel,
footerViewModelFactory,
emptyShadeViewModelFactory,
Optional.of(notificationListLoggerViewModel),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
index 6a995c08ecae..2c5aed40b222 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
@@ -17,7 +17,13 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
val Kosmos.headsUpNotificationViewBinder by
- Kosmos.Fixture { HeadsUpNotificationViewBinder(viewModel = notificationListViewModel) }
+ Kosmos.Fixture {
+ HeadsUpNotificationViewBinder(
+ viewModel = notificationListViewModel,
+ ongoingActivityChipsViewModel = ongoingActivityChipsViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
index 8ff7c7d01fb3..3e96fd7c729f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.phone.ongoingcall.shared.model
import android.app.PendingIntent
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -38,6 +40,7 @@ fun inCallModel(
notificationKey: String = "test",
appName: String = "",
promotedContent: PromotedNotificationContentModel? = null,
+ isAppVisible: Boolean = false,
) =
OngoingCallModel.InCall(
startTimeMs,
@@ -46,6 +49,7 @@ fun inCallModel(
notificationKey,
appName,
promotedContent,
+ isAppVisible,
)
object OngoingCallTestHelper {
@@ -77,8 +81,10 @@ object OngoingCallTestHelper {
contentIntent: PendingIntent? = null,
uid: Int = DEFAULT_UID,
appName: String = "Fake name",
+ isAppVisible: Boolean = false,
) {
if (StatusBarChipsModernization.isEnabled) {
+ activityManagerRepository.fake.startingIsAppVisibleValue = isAppVisible
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = key,
@@ -100,6 +106,7 @@ object OngoingCallTestHelper {
notificationKey = key,
appName = appName,
promotedContent = promotedContent,
+ isAppVisible = isAppVisible,
)
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt
new file mode 100644
index 000000000000..d9a327b99c7f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+import com.android.systemui.util.carrierConfigTracker
+
+@ExperimentalKairosApi
+val Kosmos.mobileIconsInteractorKairos by ActivatedKairosFixture {
+ MobileIconsInteractorKairosImpl(
+ mobileConnectionsRepositoryKairos,
+ carrierConfigTracker,
+ logcatTableLogBuffer(this),
+ connectivityRepository,
+ userSetupRepository,
+ applicationContext,
+ featureFlagsClassic,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt
new file mode 100644
index 000000000000..3ee33802e9d5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mobile.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.stackedMobileIconViewModelKairos by
+ Kosmos.Fixture { StackedMobileIconViewModelKairos(mobileIconsViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index a97c651ba426..5c4deaadffd5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -25,6 +25,7 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.table.tableLogBufferFactory
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeDisplaysInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
@@ -40,29 +41,34 @@ import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStat
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.homeStatusBarInteractor
var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
+ Kosmos.Fixture { homeStatusBarViewModelFactory.invoke(testableContext.displayId) }
+var Kosmos.homeStatusBarViewModelFactory: (Int) -> HomeStatusBarViewModel by
Kosmos.Fixture {
- HomeStatusBarViewModelImpl(
- testableContext.displayId,
- batteryViewModelFactory,
- tableLogBufferFactory,
- homeStatusBarInteractor,
- homeStatusBarIconBlockListInteractor,
- lightsOutInteractor,
- activeNotificationsInteractor,
- darkIconInteractor,
- headsUpNotificationInteractor,
- keyguardTransitionInteractor,
- keyguardInteractor,
- statusBarOperatorNameViewModel,
- sceneInteractor,
- sceneContainerOcclusionInteractor,
- shadeInteractor,
- shareToAppChipViewModel,
- ongoingActivityChipsViewModel,
- statusBarPopupChipsViewModelFactory,
- systemStatusEventAnimationInteractor,
- multiDisplayStatusBarContentInsetsViewModelStore,
- backgroundScope,
- testDispatcher,
- )
+ { displayId ->
+ HomeStatusBarViewModelImpl(
+ displayId,
+ batteryViewModelFactory,
+ tableLogBufferFactory,
+ homeStatusBarInteractor,
+ homeStatusBarIconBlockListInteractor,
+ lightsOutInteractor,
+ activeNotificationsInteractor,
+ darkIconInteractor,
+ headsUpNotificationInteractor,
+ keyguardTransitionInteractor,
+ keyguardInteractor,
+ statusBarOperatorNameViewModel,
+ sceneInteractor,
+ sceneContainerOcclusionInteractor,
+ shadeInteractor,
+ shareToAppChipViewModel,
+ ongoingActivityChipsViewModel,
+ statusBarPopupChipsViewModelFactory,
+ systemStatusEventAnimationInteractor,
+ multiDisplayStatusBarContentInsetsViewModelStore,
+ backgroundScope,
+ testDispatcher,
+ { shadeDisplaysInteractor },
+ )
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt
new file mode 100644
index 000000000000..2d2a81586515
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeSqueezeEffectRepository : SqueezeEffectRepository {
+ override val isSqueezeEffectEnabled = MutableStateFlow(false)
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt
new file mode 100644
index 000000000000..aa8bb6b1e104
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.topwindoweffects.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeSqueezeEffectRepository by Kosmos.Fixture { FakeSqueezeEffectRepository() } \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt
index 48cd345b1f68..6c10526a17fe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt
@@ -24,7 +24,7 @@ import com.google.common.truth.Correspondence
object FakeUiEvent {
val EVENT_ID =
Correspondence.transforming<FakeUiEvent, Int>(
- { it?.eventId },
+ { it.eventId },
"has a eventId of",
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt
index 3f0a95248d9c..9664bf3d6cc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt
@@ -23,7 +23,7 @@ import com.google.common.truth.Correspondence
object LogMaker {
val CATEGORY =
Correspondence.transforming<LogMaker, Int>(
- { it?.category },
+ { it.category },
"has a category of",
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
index 933c351679a4..6bb908a6ef07 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
@@ -22,5 +22,9 @@ import com.android.systemui.user.data.repository.userRepository
val Kosmos.userLockedInteractor by
Kosmos.Fixture {
- UserLockedInteractor(backgroundDispatcher = testDispatcher, userRepository = userRepository)
+ UserLockedInteractor(
+ backgroundDispatcher = testDispatcher,
+ userRepository = userRepository,
+ selectedUserInteractor = selectedUserInteractor,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt
index 3cec5a9cd822..58473693954c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceShowLockscreenInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.interactor
+package com.android.systemui.util
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mockFixture
-val Kosmos.keyguardServiceShowLockscreenInteractor by
- Kosmos.Fixture { KeyguardServiceShowLockscreenInteractor(backgroundScope = testScope) }
+var Kosmos.carrierConfigTracker: CarrierConfigTracker by mockFixture()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
index 4ca044d60f3f..34218646b818 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
@@ -33,6 +33,8 @@ import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSlide
import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel
import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder
import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder
import com.android.systemui.volume.mediaControllerRepository
@@ -60,6 +62,9 @@ fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDial
}
}
+ override fun sliderViewModel(): VolumeDialogSliderViewModel =
+ localKosmos.volumeDialogSliderViewModel
+
override fun sliderViewBinder(): VolumeDialogSliderViewBinder =
localKosmos.volumeDialogSliderViewBinder
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 66bb803c182d..fd90d179debc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.asStateFlow
class FakeWallpaperRepository : WallpaperRepository {
private val _wallpaperInfo: MutableStateFlow<WallpaperInfo?> = MutableStateFlow(null)
override val wallpaperInfo: StateFlow<WallpaperInfo?> = _wallpaperInfo.asStateFlow()
+ private val _lockscreenWallpaperInfo: MutableStateFlow<WallpaperInfo?> = MutableStateFlow(null)
+ override val lockscreenWallpaperInfo: StateFlow<WallpaperInfo?> =
+ _lockscreenWallpaperInfo.asStateFlow()
private val _wallpaperSupportsAmbientMode = MutableStateFlow(false)
override val wallpaperSupportsAmbientMode: Flow<Boolean> =
_wallpaperSupportsAmbientMode.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index 1761503b2cc9..b4a44751c43b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.wallpapers.data.repository
import android.content.applicationContext
import com.android.app.wallpaperManager
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
@@ -35,5 +36,6 @@ val Kosmos.wallpaperRepository by Fixture {
userRepository = userRepository,
wallpaperManager = wallpaperManager,
secureSettings = fakeSettings,
+ configurationInteractor = configurationInteractor,
)
}
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 97574e6e35e3..aad534c3289c 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -29,7 +29,10 @@ filegroup {
"vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java",
],
}),
- visibility: ["//frameworks/base/services/core"],
+ visibility: [
+ "//frameworks/base/services/core",
+ "//packages/modules/Connectivity/service-t",
+ ],
}
// TODO: b/374174952 This library is only used in "service-connectivity-b-platform"
diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
index b9dcc6160d68..aac217b3cc7a 100644
--- a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
+++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
@@ -37,9 +37,27 @@ public final class ConnectivityServiceInitializerB extends SystemService {
private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName();
private final VcnManagementService mVcnManagementService;
+ // STOPSHIP: b/385203616 This static flag is for handling a temporary case when the mainline
+ // module prebuilt has updated to register the VCN but the platform change to remove
+ // registration is not merged. After mainline prebuilt is updated, we should merge the platform
+ // ASAP and remove this static check. This check is safe because both mainline and platform
+ // registration are triggered from the same method on the same thread.
+ private static boolean sIsRegistered = false;
+
public ConnectivityServiceInitializerB(Context context) {
super(context);
- mVcnManagementService = VcnManagementService.create(context);
+
+ if (!sIsRegistered) {
+ mVcnManagementService = VcnManagementService.create(context);
+ sIsRegistered = true;
+ } else {
+ mVcnManagementService = null;
+ Log.e(
+ TAG,
+ "Ignore this registration since VCN is already registered. It will happen when"
+ + " the mainline module prebuilt has updated to register the VCN but the"
+ + " platform change to remove registration is not merged.");
+ }
}
@Override
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index ccbc46fdb03b..5424ac3bd897 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -16,7 +16,7 @@ filegroup {
srcs: [
"texts/ravenwood-common-policies.txt",
],
- visibility: ["//visibility:private"],
+ visibility: [":__subpackages__"],
}
filegroup {
@@ -44,6 +44,22 @@ filegroup {
}
filegroup {
+ name: "ravenwood-standard-annotations",
+ srcs: [
+ "texts/ravenwood-standard-annotations.txt",
+ ],
+ visibility: [":__subpackages__"],
+}
+
+filegroup {
+ name: "ravenizer-standard-options",
+ srcs: [
+ "texts/ravenizer-standard-options.txt",
+ ],
+ visibility: [":__subpackages__"],
+}
+
+filegroup {
name: "ravenwood-annotation-allowed-classes",
srcs: [
"texts/ravenwood-annotation-allowed-classes.txt",
diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp
index e36677189e02..f5b075b17fd4 100644
--- a/ravenwood/Framework.bp
+++ b/ravenwood/Framework.bp
@@ -33,6 +33,7 @@ genrule_defaults {
":ravenwood-common-policies",
":ravenwood-framework-policies",
":ravenwood-standard-options",
+ ":ravenwood-standard-annotations",
":ravenwood-annotation-allowed-classes",
],
out: [
@@ -44,6 +45,7 @@ genrule_defaults {
framework_minus_apex_cmd = "$(location hoststubgen) " +
"@$(location :ravenwood-standard-options) " +
+ "@$(location :ravenwood-standard-annotations) " +
"--debug-log $(location hoststubgen_framework-minus-apex.log) " +
"--out-jar $(location ravenwood.jar) " +
"--in-jar $(location :framework-minus-apex-for-host) " +
@@ -178,6 +180,7 @@ java_genrule {
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
"@$(location :ravenwood-standard-options) " +
+ "@$(location :ravenwood-standard-annotations) " +
"--debug-log $(location hoststubgen_services.core.log) " +
"--stats-file $(location hoststubgen_services.core_stats.csv) " +
@@ -196,6 +199,7 @@ java_genrule {
":ravenwood-common-policies",
":ravenwood-services-policies",
":ravenwood-standard-options",
+ ":ravenwood-standard-annotations",
":ravenwood-annotation-allowed-classes",
],
out: [
@@ -247,6 +251,7 @@ java_genrule {
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
"@$(location :ravenwood-standard-options) " +
+ "@$(location :ravenwood-standard-annotations) " +
"--debug-log $(location hoststubgen_core-icu4j-for-host.log) " +
"--stats-file $(location hoststubgen_core-icu4j-for-host_stats.csv) " +
@@ -265,6 +270,7 @@ java_genrule {
":ravenwood-common-policies",
":icu-ravenwood-policies",
":ravenwood-standard-options",
+ ":ravenwood-standard-annotations",
],
out: [
"ravenwood.jar",
@@ -301,6 +307,7 @@ java_genrule {
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
"@$(location :ravenwood-standard-options) " +
+ "@$(location :ravenwood-standard-annotations) " +
"--debug-log $(location framework-configinfrastructure.log) " +
"--stats-file $(location framework-configinfrastructure_stats.csv) " +
@@ -319,6 +326,7 @@ java_genrule {
":ravenwood-common-policies",
":framework-configinfrastructure-ravenwood-policies",
":ravenwood-standard-options",
+ ":ravenwood-standard-annotations",
],
out: [
"ravenwood.jar",
@@ -355,6 +363,7 @@ java_genrule {
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
"@$(location :ravenwood-standard-options) " +
+ "@$(location :ravenwood-standard-annotations) " +
"--debug-log $(location framework-statsd.log) " +
"--stats-file $(location framework-statsd_stats.csv) " +
@@ -373,6 +382,7 @@ java_genrule {
":ravenwood-common-policies",
":framework-statsd-ravenwood-policies",
":ravenwood-standard-options",
+ ":ravenwood-standard-annotations",
],
out: [
"ravenwood.jar",
@@ -409,6 +419,7 @@ java_genrule {
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
"@$(location :ravenwood-standard-options) " +
+ "@$(location :ravenwood-standard-annotations) " +
"--debug-log $(location framework-graphics.log) " +
"--stats-file $(location framework-graphics_stats.csv) " +
@@ -427,6 +438,7 @@ java_genrule {
":ravenwood-common-policies",
":framework-graphics-ravenwood-policies",
":ravenwood-standard-options",
+ ":ravenwood-standard-annotations",
],
out: [
"ravenwood.jar",
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index df63cb9dfc50..083d2aa15316 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -150,6 +150,10 @@
"host": true
},
{
+ "name": "RavenwoodCoreTest-light",
+ "host": true
+ },
+ {
"name": "RavenwoodMinimumTest",
"host": true
},
@@ -168,13 +172,19 @@
{
"name": "RavenwoodServicesTest",
"host": true
+ },
+ {
+ "name": "UinputTestsRavenwood",
+ "host": true
}
// AUTO-GENERATED-END
],
"ravenwood-postsubmit": [
- {
- "name": "SystemUiRavenTests",
- "host": true
- }
+ // We haven't maintained SystemUiRavenTests, and as a result, it's been demoted already.
+ // Disable it until we fix the issues: b/319647875
+ // {
+ // "name": "SystemUiRavenTests",
+ // "host": true
+ // }
]
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index d935626c34df..d8741975c71a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -23,7 +23,9 @@ import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACK
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
@@ -271,6 +273,13 @@ public class RavenwoodRuntimeEnvironmentController {
dumpJavaProperties();
dumpOtherInfo();
+ System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+ var runtimePath = getRavenwoodRuntimePath();
+ System.setProperty(RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP, runtimePath);
+
+ Log.i(TAG, "PWD=" + System.getProperty("user.dir"));
+ Log.i(TAG, "RuntimePath=" + runtimePath);
+
// Make sure libravenwood_runtime is loaded.
System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
@@ -314,7 +323,6 @@ public class RavenwoodRuntimeEnvironmentController {
Typeface.loadPreinstalledSystemFontMap();
Typeface.loadNativeSystemFonts();
- System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
// This will let AndroidJUnit4 use the original runner.
System.setProperty("android.junit.runner",
"androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
index 3e2c4051b792..f25ae6a34242 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
@@ -128,28 +128,6 @@ public class RavenwoodUtils {
runOnHandlerSync(getMainHandler(), r);
}
- public static class MockitoHelper {
- private MockitoHelper() {
- }
-
- /**
- * Allow verifyZeroInteractions to work on ravenwood. It was replaced with a different
- * method on. (Maybe we should do it in Ravenizer.)
- */
- public static void verifyZeroInteractions(Object... mocks) {
- if (RavenwoodRule.isOnRavenwood()) {
- // Mockito 4 or later
- reflectMethod("org.mockito.Mockito", "verifyNoInteractions", Object[].class)
- .callStatic(new Object[]{mocks});
- } else {
- // Mockito 2
- reflectMethod("org.mockito.Mockito", "verifyZeroInteractions", Object[].class)
- .callStatic(new Object[]{mocks});
- }
- }
- }
-
-
/**
* Wrap the given {@link Supplier} to become memoized.
*
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 893b354d4645..e1b537e11842 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -68,6 +68,8 @@ public class RavenwoodCommonUtils {
RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk";
public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
+ public static final String RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP =
+ "android.ravenwood.runtime_path";
/**
* @return if we're running on Ravenwood.
diff --git a/ravenwood/scripts/extract-last-soong-commands.py b/ravenwood/scripts/extract-last-soong-commands.py
index 0629b77029e0..b8d6f2042389 100755
--- a/ravenwood/scripts/extract-last-soong-commands.py
+++ b/ravenwood/scripts/extract-last-soong-commands.py
@@ -55,7 +55,7 @@ def main(args):
if s.startswith("verbose"):
continue
- if re.match('^\[.*bootstrap blueprint', s):
+ if re.match('^\\[.*bootstrap blueprint', s):
continue
s = s.rstrip()
diff --git a/ravenwood/scripts/list-ravenwood-tests.sh b/ravenwood/scripts/list-ravenwood-tests.sh
index 05f3fdffdaa7..5d7daeb1c304 100755
--- a/ravenwood/scripts/list-ravenwood-tests.sh
+++ b/ravenwood/scripts/list-ravenwood-tests.sh
@@ -15,4 +15,20 @@
# List all the ravenwood test modules.
-jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json" | sort
+set -e
+
+in="$OUT/module-info.json"
+cache="$OUT/ravenwood-test-list.cached.tmp"
+cache_temp="$OUT/ravenwood-test-list.temp.tmp"
+
+if [[ "$in" -nt "$cache" ]] ; then
+ rm -f "$cache_temp" "$cache"
+
+ # First, create to a temp file, and once it's completed, rename it
+ # to the actual cache file, so that if the command failed or is interrupted,
+ # we don't update the cache.
+ jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json" | sort > "$cache_temp"
+ mv "$cache_temp" "$cache"
+fi
+
+cat "$cache"
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
index fd6d6fb66465..b3af8753ee83 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
@@ -25,7 +25,6 @@ import com.android.internal.os.Flags
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,7 +39,6 @@ class RavenwoodAconfigSimpleReadTests {
}
@Test
- @Ignore // TODO: Enable this test after rolling out the "2" flags.
fun testTrueFlags() {
assertTrue(Flags.ravenwoodFlagRo2())
assertTrue(Flags.ravenwoodFlagRw2())
@@ -67,14 +65,12 @@ class RavenwoodAconfigCheckFlagsRuleTests {
@Test
@RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2)
- @Ignore // TODO: Enable this test after rolling out the "2" flags.
fun testRequireFlagsDisabledRo() {
fail("This test shouldn't be executed")
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2)
- @Ignore // TODO: Enable this test after rolling out the "2" flags.
fun testRequireFlagsDisabledRw() {
fail("This test shouldn't be executed")
}
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodJdkPatchTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodJdkPatchTest.java
new file mode 100644
index 000000000000..cdfd4a877f43
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodJdkPatchTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import org.junit.Test;
+
+import java.io.FileDescriptor;
+import java.util.LinkedHashMap;
+import java.util.regex.Pattern;
+
+public class RavenwoodJdkPatchTest {
+
+ @Test
+ public void testUnicodeRegex() {
+ var pattern = Pattern.compile("\\w+");
+ assertTrue(pattern.matcher("über").matches());
+ }
+
+ @Test
+ public void testLinkedHashMapEldest() {
+ var map = new LinkedHashMap<String, String>();
+ map.put("a", "b");
+ map.put("x", "y");
+ assertEquals(map.entrySet().iterator().next(), map.eldest());
+ }
+
+ @Test
+ public void testFileDescriptorGetSetInt() throws ErrnoException {
+ FileDescriptor fd = Os.open("/dev/zero", OsConstants.O_RDONLY, 0);
+ try {
+ int fdRaw = fd.getInt$();
+ assertNotEquals(-1, fdRaw);
+ fd.setInt$(-1);
+ assertEquals(-1, fd.getInt$());
+ fd.setInt$(fdRaw);
+ Os.close(fd);
+ assertEquals(-1, fd.getInt$());
+ } finally {
+ Os.close(fd);
+ }
+ }
+}
diff --git a/ravenwood/tests/resapk_test/Android.bp b/ravenwood/tests/resapk_test/Android.bp
index c14576550f78..960b3ed0013a 100644
--- a/ravenwood/tests/resapk_test/Android.bp
+++ b/ravenwood/tests/resapk_test/Android.bp
@@ -10,7 +10,7 @@ package {
android_ravenwood_test {
name: "RavenwoodResApkTest",
- resource_apk: "RavenwoodResApkTest-apk",
+ resource_apk: "RavenwoodResApkTest-res",
libs: [
// Normally, tests shouldn't directly access it, but we need to access RavenwoodCommonUtils
@@ -24,6 +24,7 @@ android_ravenwood_test {
],
srcs: [
"test/**/*.java",
+ ":RavenwoodResApkTest-res{.aapt.srcjar}",
],
sdk_version: "test_current",
auto_gen_config: true,
diff --git a/ravenwood/tests/resapk_test/apk/Android.bp b/ravenwood/tests/resapk_test/apk/Android.bp
index 10ed5e2f8410..fd8976df4316 100644
--- a/ravenwood/tests/resapk_test/apk/Android.bp
+++ b/ravenwood/tests/resapk_test/apk/Android.bp
@@ -8,7 +8,13 @@ package {
}
android_app {
- name: "RavenwoodResApkTest-apk",
+ name: "RavenwoodResApkTest-res",
sdk_version: "current",
+
+ use_resource_processor: false,
+
+ flags_packages: [
+ "com.android.internal.os.flags-aconfig",
+ ],
}
diff --git a/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml b/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml
new file mode 100644
index 000000000000..17cdb868fc6b
--- /dev/null
+++ b/ravenwood/tests/resapk_test/apk/res/layout/testlayout.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <View android:id="@+id/view1" text="no-flags" />
+ <View android:id="@+id/view2" text="ro-enabled" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_2"/>
+ <View android:id="@+id/view3" text="ro-disabled" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_1"/>
+ <View android:id="@+id/view2" text="rw-enabled" android:featureFlag="com.android.internal.os.ravenwood_flag_rw_2"/>
+ <View android:id="@+id/view3" text="rw-disabled" android:featureFlag="com.android.internal.os.ravenwood_flag_rw_1"/>
+</LinearLayout>
diff --git a/ravenwood/tests/resapk_test/apk/res/values/strings.xml b/ravenwood/tests/resapk_test/apk/res/values/strings.xml
index 23d4c0f21007..5abf7475caa7 100644
--- a/ravenwood/tests/resapk_test/apk/res/values/strings.xml
+++ b/ravenwood/tests/resapk_test/apk/res/values/strings.xml
@@ -13,7 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Test string 1 -->
<string name="test_string_1" translatable="false" >Test String 1</string>
+ <!-- values can only use readonly flags -->
+ <string name="test_string_enabled" translatable="false" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_2">Enabled</string>
+ <string name="test_string_disabled" translatable="false" android:featureFlag="com.android.internal.os.ravenwood_flag_ro_1">Disabled</string>
</resources>
diff --git a/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
index e547114bbe40..89f8d40da7d4 100644
--- a/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
+++ b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
@@ -16,23 +16,41 @@
package com.android.ravenwoodtest.resapk_test;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.util.Log;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.restest_apk.R;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class RavenwoodResApkTest {
+ private static final String TAG = "RavenwoodResApkTest";
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
/**
* Ensure the file "ravenwood-res.apk" exists.
- * TODO Check the content of it, once Ravenwood supports resources. The file should
- * be a copy of RavenwoodResApkTest-apk.apk
*/
@Test
public void testResApkExists() {
@@ -48,4 +66,73 @@ public class RavenwoodResApkTest {
assertTrue(new File(
RavenwoodCommonUtils.getRavenwoodRuntimePath() + "/" + file).exists());
}
+
+ @Test
+ public void testReadStringNoFlag() {
+ assertThat(sContext.getString(R.string.test_string_1)).isEqualTo("Test String 1");
+ }
+
+ @Test
+ public void testReadStringRoFlagEnabled() {
+ assertThat(sContext.getString(R.string.test_string_enabled)).isEqualTo("Enabled");
+ }
+
+ @Test
+ public void testReadStringRoFlagDisabled() {
+ assertThrows(android.content.res.Resources.NotFoundException.class, () -> {
+ sContext.getString(R.string.test_string_disabled);
+ });
+ }
+
+ /**
+ * Look into the layout and collect the "text" attribute.
+ *
+ * It _should_ respect android:featureFlag, but until b/396458006 gets fixed, this returns
+ * even disabled elements.
+ */
+ private List<String> getTextsFromEnabledChildren() throws Exception {
+ try (XmlResourceParser parser = sContext.getResources().getLayout(R.layout.testlayout)) {
+ assertNotNull(parser);
+
+ var ret = new ArrayList<String>();
+
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ var text = parser.getAttributeValue(null, "text");
+ if (text == null) {
+ continue;
+ }
+
+ Log.d(TAG, "Found tag: " + parser.getName() + " text='" + text + "'");
+ ret.add(text);
+ }
+ return ret;
+ }
+ }
+
+ @Test
+ public void testElementNoFlag() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).contains("no-flags");
+ }
+
+ @Test
+ public void testElementWithRoFlagEnabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).contains("ro-enabled");
+ }
+
+ @Test
+ public void testElementWithRoFlagDisabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).doesNotContain("ro-disabled");
+ }
+
+ @Test
+ public void testElementWithRwFlagEnabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).contains("rw-enabled");
+ }
+
+ @Test
+ @DisabledOnRavenwood(bug = 396458006,
+ reason = "RW flags in XML are all handled as enabled for now")
+ public void testElementWithRwFlagDisabled() throws Exception {
+ assertThat(getTextsFromEnabledChildren()).doesNotContain("rw-disabled");
+ }
}
diff --git a/ravenwood/texts/ravenizer-standard-options.txt b/ravenwood/texts/ravenizer-standard-options.txt
new file mode 100644
index 000000000000..cef736f87e72
--- /dev/null
+++ b/ravenwood/texts/ravenizer-standard-options.txt
@@ -0,0 +1,13 @@
+# File containing standard options to Ravenizer for Ravenwood
+
+# Keep all classes / methods / fields in tests and its target
+--default-keep
+
+--delete-finals
+
+# Include standard annotations
+@jar:texts/ravenwood-standard-annotations.txt
+
+# Apply common policies
+--policy-override-file
+ jar:texts/ravenwood-common-policies.txt
diff --git a/ravenwood/texts/ravenwood-standard-annotations.txt b/ravenwood/texts/ravenwood-standard-annotations.txt
new file mode 100644
index 000000000000..75ec5cadb6fc
--- /dev/null
+++ b/ravenwood/texts/ravenwood-standard-annotations.txt
@@ -0,0 +1,37 @@
+# Standard annotations.
+# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
+--keep-annotation
+ android.ravenwood.annotation.RavenwoodKeep
+
+--keep-annotation
+ android.ravenwood.annotation.RavenwoodKeepPartialClass
+
+--keep-class-annotation
+ android.ravenwood.annotation.RavenwoodKeepWholeClass
+
+--throw-annotation
+ android.ravenwood.annotation.RavenwoodThrow
+
+--remove-annotation
+ android.ravenwood.annotation.RavenwoodRemove
+
+--ignore-annotation
+ android.ravenwood.annotation.RavenwoodIgnore
+
+--partially-allowed-annotation
+ android.ravenwood.annotation.RavenwoodPartiallyAllowlisted
+
+--substitute-annotation
+ android.ravenwood.annotation.RavenwoodReplace
+
+--redirect-annotation
+ android.ravenwood.annotation.RavenwoodRedirect
+
+--redirection-class-annotation
+ android.ravenwood.annotation.RavenwoodRedirectionClass
+
+--class-load-hook-annotation
+ android.ravenwood.annotation.RavenwoodClassLoadHook
+
+--keep-static-initializer-annotation
+ android.ravenwood.annotation.RavenwoodKeepStaticInitializer
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index 233657557747..0a650254a71f 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -15,41 +15,3 @@
#--default-class-load-hook
# com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
-
-# Standard annotations.
-# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
---keep-annotation
- android.ravenwood.annotation.RavenwoodKeep
-
---keep-annotation
- android.ravenwood.annotation.RavenwoodKeepPartialClass
-
---keep-class-annotation
- android.ravenwood.annotation.RavenwoodKeepWholeClass
-
---throw-annotation
- android.ravenwood.annotation.RavenwoodThrow
-
---remove-annotation
- android.ravenwood.annotation.RavenwoodRemove
-
---ignore-annotation
- android.ravenwood.annotation.RavenwoodIgnore
-
---partially-allowed-annotation
- android.ravenwood.annotation.RavenwoodPartiallyAllowlisted
-
---substitute-annotation
- android.ravenwood.annotation.RavenwoodReplace
-
---redirect-annotation
- android.ravenwood.annotation.RavenwoodRedirect
-
---redirection-class-annotation
- android.ravenwood.annotation.RavenwoodRedirectionClass
-
---class-load-hook-annotation
- android.ravenwood.annotation.RavenwoodClassLoadHook
-
---keep-static-initializer-annotation
- android.ravenwood.annotation.RavenwoodKeepStaticInitializer
diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp
index a5ff4964b0a4..004834eed983 100644
--- a/ravenwood/tools/hoststubgen/Android.bp
+++ b/ravenwood/tools/hoststubgen/Android.bp
@@ -90,11 +90,9 @@ java_library {
java_library_host {
name: "hoststubgen-lib",
defaults: ["ravenwood-internal-only-visibility-java"],
- srcs: ["src/**/*.kt"],
+ srcs: ["lib/**/*.kt"],
static_libs: [
"hoststubgen-helper-runtime",
- ],
- libs: [
"junit",
"ow2-asm",
"ow2-asm-analysis",
@@ -108,15 +106,8 @@ java_library_host {
java_binary_host {
name: "hoststubgen",
main_class: "com.android.hoststubgen.HostStubGenMain",
- static_libs: [
- "hoststubgen-lib",
- "junit",
- "ow2-asm",
- "ow2-asm-analysis",
- "ow2-asm-commons",
- "ow2-asm-tree",
- "ow2-asm-util",
- ],
+ srcs: ["src/**/*.kt"],
+ static_libs: ["hoststubgen-lib"],
visibility: ["//visibility:public"],
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Exceptions.kt
index f59e143c1e4e..ae0a00855650 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Exceptions.kt
@@ -15,8 +15,6 @@
*/
package com.android.hoststubgen
-import java.io.File
-
/**
* We will not print the stack trace for exceptions implementing it.
*/
@@ -64,9 +62,6 @@ class DuplicateAnnotationException(annotationName: String?) :
class InputFileNotFoundException(filename: String) :
ArgumentsException("File '$filename' not found")
-fun String.ensureFileExists(): String {
- if (!File(this).exists()) {
- throw InputFileNotFoundException(this)
- }
- return this
-}
+/** Thrown when a JAR resource does not exist. */
+class JarResourceNotFoundException(path: String) :
+ ArgumentsException("JAR resource '$path' not found")
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt
new file mode 100644
index 000000000000..98f96a89d889
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.filters.AnnotationBasedFilter
+import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
+import com.android.hoststubgen.filters.ConstantFilter
+import com.android.hoststubgen.filters.DefaultHookInjectingFilter
+import com.android.hoststubgen.filters.FilterRemapper
+import com.android.hoststubgen.filters.ImplicitOutputFilter
+import com.android.hoststubgen.filters.KeepNativeFilter
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.filters.SanitizationFilter
+import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
+import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+import com.android.hoststubgen.utils.ClassPredicate
+import com.android.hoststubgen.visitors.BaseAdapter
+import com.android.hoststubgen.visitors.ImplGeneratingAdapter
+import com.android.hoststubgen.visitors.PackageRedirectRemapper
+import java.io.PrintWriter
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.commons.ClassRemapper
+import org.objectweb.asm.util.CheckClassAdapter
+import org.objectweb.asm.util.TraceClassVisitor
+
+/**
+ * This class implements bytecode transformation of HostStubGen.
+ */
+class HostStubGenClassProcessor(
+ private val options: HostStubGenClassProcessorOptions,
+ val allClasses: ClassNodes,
+ private val errors: HostStubGenErrors = HostStubGenErrors(),
+ private val stats: HostStubGenStats? = null,
+) {
+ val filter = buildFilter()
+ val remapper = FilterRemapper(filter)
+
+ private val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
+ private val processedAnnotation = setOf(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR)
+
+ /**
+ * Build the filter, which decides what classes/methods/fields should be put in stub or impl
+ * jars, and "how". (e.g. with substitution?)
+ */
+ private fun buildFilter(): OutputFilter {
+ // We build a "chain" of multiple filters here.
+ //
+ // The filters are build in from "inside", meaning the first filter created here is
+ // the last filter used, so it has the least precedence.
+ //
+ // So, for example, the "remove" annotation, which is handled by AnnotationBasedFilter,
+ // can override a class-wide annotation, which is handled by
+ // ClassWidePolicyPropagatingFilter, and any annotations can be overridden by the
+ // text-file based filter, which is handled by parseTextFilterPolicyFile.
+
+ // The first filter is for the default policy from the command line options.
+ var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options")
+
+ // Next, we build a filter that preserves all native methods by default
+ filter = KeepNativeFilter(allClasses, filter)
+
+ // Next, we need a filter that resolves "class-wide" policies.
+ // This is used when a member (methods, fields, nested classes) don't get any policies
+ // from upper filters. e.g. when a method has no annotations, then this filter will apply
+ // the class-wide policy, if any. (if not, we'll fall back to the above filter.)
+ filter = ClassWidePolicyPropagatingFilter(allClasses, filter)
+
+ // Inject default hooks from options.
+ filter = DefaultHookInjectingFilter(
+ allClasses,
+ options.defaultClassLoadHook.get,
+ options.defaultMethodCallHook.get,
+ filter
+ )
+
+ val annotationAllowedPredicate = options.annotationAllowedClassesFile.get.let { file ->
+ if (file == null) {
+ ClassPredicate.newConstantPredicate(true) // Allow all classes
+ } else {
+ ClassPredicate.loadFromFile(file, false)
+ }
+ }
+
+ // Next, Java annotation based filter.
+ val annotFilter = AnnotationBasedFilter(
+ errors,
+ allClasses,
+ options.keepAnnotations,
+ options.keepClassAnnotations,
+ options.throwAnnotations,
+ options.removeAnnotations,
+ options.ignoreAnnotations,
+ options.substituteAnnotations,
+ options.redirectAnnotations,
+ options.redirectionClassAnnotations,
+ options.classLoadHookAnnotations,
+ options.partiallyAllowedAnnotations,
+ options.keepStaticInitializerAnnotations,
+ annotationAllowedPredicate,
+ filter
+ )
+ filter = annotFilter
+
+ // Next, "text based" filter, which allows to override policies without touching
+ // the target code.
+ if (options.policyOverrideFiles.isNotEmpty()) {
+ val builder = TextFileFilterPolicyBuilder(allClasses, filter)
+ options.policyOverrideFiles.forEach(builder::parse)
+ filter = builder.createOutputFilter()
+ annotFilter.annotationAllowedMembers = builder.annotationAllowedMembersFilter
+ }
+
+ // Apply the implicit filter.
+ filter = ImplicitOutputFilter(errors, allClasses, filter)
+
+ // Add a final sanitization step.
+ filter = SanitizationFilter(errors, allClasses, filter)
+
+ return filter
+ }
+
+ private fun buildVisitor(base: ClassVisitor, className: String): ClassVisitor {
+ // Connect to the base visitor
+ var outVisitor: ClassVisitor = base
+
+ if (options.enableClassChecker.get) {
+ outVisitor = CheckClassAdapter(outVisitor)
+ }
+
+ // Remapping should happen at the end.
+ outVisitor = ClassRemapper(outVisitor, remapper)
+
+ val visitorOptions = BaseAdapter.Options(
+ errors = errors,
+ stats = stats,
+ deleteClassFinals = options.deleteFinals.get,
+ deleteMethodFinals = options.deleteFinals.get,
+ )
+
+ val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose))
+
+ // Inject TraceClassVisitor for debugging.
+ if (options.enablePostTrace.get) {
+ outVisitor = TraceClassVisitor(outVisitor, verbosePrinter)
+ }
+
+ // Handle --package-redirect
+ if (!packageRedirector.isEmpty) {
+ // Don't apply the remapper on redirect-from classes.
+ // Otherwise, if the target jar actually contains the "from" classes (which
+ // may or may not be the case) they'd be renamed.
+ // But we update all references in other places, so, a method call to a "from" class
+ // would be replaced with the "to" class. All type references (e.g. variable types)
+ // will be updated too.
+ if (!packageRedirector.isTarget(className)) {
+ outVisitor = ClassRemapper(outVisitor, packageRedirector)
+ } else {
+ log.v(
+ "Class $className is a redirect-from class, not applying" +
+ " --package-redirect"
+ )
+ }
+ }
+
+ outVisitor = ImplGeneratingAdapter(allClasses, outVisitor, filter, visitorOptions)
+
+ // Inject TraceClassVisitor for debugging.
+ if (options.enablePreTrace.get) {
+ outVisitor = TraceClassVisitor(outVisitor, verbosePrinter)
+ }
+
+ return outVisitor
+ }
+
+ fun processClassBytecode(bytecode: ByteArray): ByteArray {
+ val cr = ClassReader(bytecode)
+
+ // If the class was already processed previously, skip
+ val clz = allClasses.getClass(cr.className)
+ if (clz.findAnyAnnotation(processedAnnotation) != null) {
+ return bytecode
+ }
+
+ // COMPUTE_FRAMES wouldn't be happy if code uses
+ val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES
+ val cw = ClassWriter(flags)
+
+ val outVisitor = buildVisitor(cw, cr.className)
+
+ cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
+ return cw.toByteArray()
+ }
+}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt
new file mode 100644
index 000000000000..e7166f11f597
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen
+
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.utils.ArgIterator
+import com.android.hoststubgen.utils.BaseOptions
+import com.android.hoststubgen.utils.FileOrResource
+import com.android.hoststubgen.utils.SetOnce
+
+private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> {
+ val colon = fromColonTo.indexOf(':')
+ if ((colon < 1) || (colon + 1 >= fromColonTo.length)) {
+ throw ArgumentsException("--package-redirect must be a colon-separated string")
+ }
+ // TODO check for duplicates
+ return Pair(fromColonTo.substring(0, colon), fromColonTo.substring(colon + 1))
+}
+
+/**
+ * Options to configure [HostStubGenClassProcessor].
+ */
+open class HostStubGenClassProcessorOptions(
+ var keepAnnotations: MutableSet<String> = mutableSetOf(),
+ var throwAnnotations: MutableSet<String> = mutableSetOf(),
+ var removeAnnotations: MutableSet<String> = mutableSetOf(),
+ var ignoreAnnotations: MutableSet<String> = mutableSetOf(),
+ var keepClassAnnotations: MutableSet<String> = mutableSetOf(),
+ var partiallyAllowedAnnotations: MutableSet<String> = mutableSetOf(),
+ var redirectAnnotations: MutableSet<String> = mutableSetOf(),
+
+ var substituteAnnotations: MutableSet<String> = mutableSetOf(),
+ var redirectionClassAnnotations: MutableSet<String> = mutableSetOf(),
+ var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(),
+ var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(),
+
+ var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
+
+ var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null),
+
+ var defaultClassLoadHook: SetOnce<String?> = SetOnce(null),
+ var defaultMethodCallHook: SetOnce<String?> = SetOnce(null),
+
+ var policyOverrideFiles: MutableList<FileOrResource> = mutableListOf(),
+
+ var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
+
+ var deleteFinals: SetOnce<Boolean> = SetOnce(false),
+
+ var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
+ var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
+ var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
+) : BaseOptions() {
+
+ private val allAnnotations = mutableSetOf<String>()
+
+ private fun ensureUniqueAnnotation(name: String): String {
+ if (!allAnnotations.add(name)) {
+ throw DuplicateAnnotationException(name)
+ }
+ return name
+ }
+
+ override fun parseOption(option: String, args: ArgIterator): Boolean {
+ // Define some shorthands...
+ fun nextArg(): String = args.nextArgRequired(option)
+ fun MutableSet<String>.addUniqueAnnotationArg(): String =
+ nextArg().also { this += ensureUniqueAnnotation(it) }
+
+ when (option) {
+ "--policy-override-file" -> policyOverrideFiles.add(FileOrResource(nextArg()))
+
+ "--default-remove" -> defaultPolicy.set(FilterPolicy.Remove)
+ "--default-throw" -> defaultPolicy.set(FilterPolicy.Throw)
+ "--default-keep" -> defaultPolicy.set(FilterPolicy.Keep)
+
+ "--keep-annotation" ->
+ keepAnnotations.addUniqueAnnotationArg()
+
+ "--keep-class-annotation" ->
+ keepClassAnnotations.addUniqueAnnotationArg()
+
+ "--partially-allowed-annotation" ->
+ partiallyAllowedAnnotations.addUniqueAnnotationArg()
+
+ "--throw-annotation" ->
+ throwAnnotations.addUniqueAnnotationArg()
+
+ "--remove-annotation" ->
+ removeAnnotations.addUniqueAnnotationArg()
+
+ "--ignore-annotation" ->
+ ignoreAnnotations.addUniqueAnnotationArg()
+
+ "--substitute-annotation" ->
+ substituteAnnotations.addUniqueAnnotationArg()
+
+ "--redirect-annotation" ->
+ redirectAnnotations.addUniqueAnnotationArg()
+
+ "--redirection-class-annotation" ->
+ redirectionClassAnnotations.addUniqueAnnotationArg()
+
+ "--class-load-hook-annotation" ->
+ classLoadHookAnnotations.addUniqueAnnotationArg()
+
+ "--keep-static-initializer-annotation" ->
+ keepStaticInitializerAnnotations.addUniqueAnnotationArg()
+
+ "--package-redirect" ->
+ packageRedirects += parsePackageRedirect(nextArg())
+
+ "--annotation-allowed-classes-file" ->
+ annotationAllowedClassesFile.set(nextArg())
+
+ "--default-class-load-hook" ->
+ defaultClassLoadHook.set(nextArg())
+
+ "--default-method-call-hook" ->
+ defaultMethodCallHook.set(nextArg())
+
+ "--delete-finals" -> deleteFinals.set(true)
+
+ // Following options are for debugging.
+ "--enable-class-checker" -> enableClassChecker.set(true)
+ "--no-class-checker" -> enableClassChecker.set(false)
+
+ "--enable-pre-trace" -> enablePreTrace.set(true)
+ "--no-pre-trace" -> enablePreTrace.set(false)
+
+ "--enable-post-trace" -> enablePostTrace.set(true)
+ "--no-post-trace" -> enablePostTrace.set(false)
+
+ else -> return false
+ }
+
+ return true
+ }
+
+ override fun dumpFields(): String {
+ return """
+ keepAnnotations=$keepAnnotations,
+ throwAnnotations=$throwAnnotations,
+ removeAnnotations=$removeAnnotations,
+ ignoreAnnotations=$ignoreAnnotations,
+ keepClassAnnotations=$keepClassAnnotations,
+ partiallyAllowedAnnotations=$partiallyAllowedAnnotations,
+ substituteAnnotations=$substituteAnnotations,
+ nativeSubstituteAnnotations=$redirectionClassAnnotations,
+ classLoadHookAnnotations=$classLoadHookAnnotations,
+ keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations,
+ packageRedirects=$packageRedirects,
+ annotationAllowedClassesFile=$annotationAllowedClassesFile,
+ defaultClassLoadHook=$defaultClassLoadHook,
+ defaultMethodCallHook=$defaultMethodCallHook,
+ policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()},
+ defaultPolicy=$defaultPolicy,
+ deleteFinals=$deleteFinals,
+ enableClassChecker=$enableClassChecker,
+ enablePreTrace=$enablePreTrace,
+ enablePostTrace=$enablePostTrace,
+ """.trimIndent()
+ }
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenErrors.kt
index a218c5599553..a218c5599553 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenErrors.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenLogger.kt
index 4bcee409aaec..4bcee409aaec 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenLogger.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
index 9045db210495..9045db210495 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
index 10179eefcb95..b2af7827f8c5 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
@@ -15,6 +15,8 @@
*/
package com.android.hoststubgen
+import java.io.PrintWriter
+
/**
* Name of this executable. Set it in the main method.
*/
@@ -96,3 +98,23 @@ class ParseException : Exception, UserErrorException {
fun csvEscape(value: String): String {
return "\"" + value.replace("\"", "\"\"") + "\""
}
+
+inline fun runMainWithBoilerplate(realMain: () -> Unit) {
+ var success = false
+
+ try {
+ realMain()
+
+ success = true
+ } catch (e: Throwable) {
+ log.e("$executableName: Error: ${e.message}")
+ if (e !is UserErrorException) {
+ e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
+ }
+ } finally {
+ log.i("$executableName finished")
+ log.flush()
+ }
+
+ System.exit(if (success) 0 else 1 )
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt
index b41ce0f65017..112ef01e20cb 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt
@@ -217,7 +217,7 @@ private val numericalInnerClassName = """.*\$\d+$""".toRegex()
fun isAnonymousInnerClass(cn: ClassNode): Boolean {
// TODO: Is there a better way?
- return cn.name.matches(numericalInnerClassName)
+ return cn.outerClass != null && cn.name.matches(numericalInnerClassName)
}
/**
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
index e2647eb13ed3..e2647eb13ed3 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
index 5e4e70f0cbaa..5e4e70f0cbaa 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 6b360b79c327..b2252092f923 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -66,7 +66,7 @@ private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean {
|| className.endsWith("/FeatureFlags")
|| className.endsWith("/FeatureFlagsImpl")
|| className.endsWith("/CustomFeatureFlags")
- || className.endsWith("/FakeFeatureFlagsImpl");
+ || className.endsWith("/FakeFeatureFlagsImpl")
}
/**
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index 73c72a21ef7b..73c72a21ef7b 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
index f8bb526d0a86..f8bb526d0a86 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ConstantFilter.kt
index be3c59c80152..be3c59c80152 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ConstantFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
index aaf49c154a17..aaf49c154a17 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DelegatingFilter.kt
index b8b0d8a31268..7f36aca33eee 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DelegatingFilter.kt
@@ -19,9 +19,9 @@ package com.android.hoststubgen.filters
* Base class for an [OutputFilter] that uses another filter as a fallback.
*/
abstract class DelegatingFilter(
- // fallback shouldn't be used by subclasses directly, so make it private.
- // They should instead be calling into `super` or `outermostFilter`.
- private val fallback: OutputFilter
+ // fallback shouldn't be used by subclasses directly, so make it private.
+ // They should instead be calling into `super` or `outermostFilter`.
+ private val fallback: OutputFilter
) : OutputFilter() {
init {
fallback.outermostFilter = this
@@ -50,24 +50,24 @@ abstract class DelegatingFilter(
}
override fun getPolicyForField(
- className: String,
- fieldName: String
+ className: String,
+ fieldName: String
): FilterPolicyWithReason {
return fallback.getPolicyForField(className, fieldName)
}
override fun getPolicyForMethod(
- className: String,
- methodName: String,
- descriptor: String
+ className: String,
+ methodName: String,
+ descriptor: String
): FilterPolicyWithReason {
return fallback.getPolicyForMethod(className, methodName, descriptor)
}
override fun getRenameTo(
- className: String,
- methodName: String,
- descriptor: String
+ className: String,
+ methodName: String,
+ descriptor: String
): String? {
return fallback.getRenameTo(className, methodName, descriptor)
}
@@ -97,13 +97,12 @@ abstract class DelegatingFilter(
}
override fun getMethodCallReplaceTo(
- callerClassName: String,
- callerMethodName: String,
className: String,
methodName: String,
descriptor: String,
): MethodReplaceTarget? {
return fallback.getMethodCallReplaceTo(
- callerClassName, callerMethodName, className, methodName, descriptor)
+ className, methodName, descriptor
+ )
}
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt
index 81c26ffdf1f4..81c26ffdf1f4 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index b10165b835f2..b10165b835f2 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterRemapper.kt
index bba4681d3838..bba4681d3838 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterRemapper.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 474da6dfa1b9..d44d016f7c5b 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -16,7 +16,6 @@
package com.android.hoststubgen.filters
import com.android.hoststubgen.HostStubGenErrors
-import com.android.hoststubgen.HostStubGenInternalException
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
import com.android.hoststubgen.asm.ClassNodes
@@ -37,19 +36,15 @@ import org.objectweb.asm.tree.ClassNode
* TODO: Do we need a way to make anonymous class methods and lambdas "throw"?
*/
class ImplicitOutputFilter(
- private val errors: HostStubGenErrors,
- private val classes: ClassNodes,
- fallback: OutputFilter
+ private val errors: HostStubGenErrors,
+ private val classes: ClassNodes,
+ fallback: OutputFilter
) : DelegatingFilter(fallback) {
- private fun getClassImplicitPolicy(className: String, cn: ClassNode): FilterPolicyWithReason? {
+ private fun getClassImplicitPolicy(cn: ClassNode): FilterPolicyWithReason? {
if (isAnonymousInnerClass(cn)) {
log.forDebug {
// log.d(" anon-inner class: ${className} outer: ${cn.outerClass} ")
}
- if (cn.outerClass == null) {
- throw HostStubGenInternalException(
- "outerClass is null for anonymous inner class")
- }
// If the outer class needs to be in impl, it should be in impl too.
val outerPolicy = outermostFilter.getPolicyForClass(cn.outerClass)
if (outerPolicy.policy.needsInOutput) {
@@ -65,15 +60,15 @@ class ImplicitOutputFilter(
val cn = classes.getClass(className)
// Use the implicit policy, if any.
- getClassImplicitPolicy(className, cn)?.let { return it }
+ getClassImplicitPolicy(cn)?.let { return it }
return fallback
}
override fun getPolicyForMethod(
- className: String,
- methodName: String,
- descriptor: String
+ className: String,
+ methodName: String,
+ descriptor: String
): FilterPolicyWithReason {
val fallback = super.getPolicyForMethod(className, methodName, descriptor)
val classPolicy = outermostFilter.getPolicyForClass(className)
@@ -84,12 +79,14 @@ class ImplicitOutputFilter(
// "keep" instead.
// Unless it's an enum -- in that case, the below code would handle it.
if (!cn.isEnum() &&
- fallback.policy == FilterPolicy.Throw &&
- methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
+ fallback.policy == FilterPolicy.Throw &&
+ methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC
+ ) {
// TODO Maybe show a warning?? But that'd be too noisy with --default-throw.
return FilterPolicy.Ignore.withReason(
"'throw' on static initializer is handled as 'ignore'" +
- " [original throw reason: ${fallback.reason}]")
+ " [original throw reason: ${fallback.reason}]"
+ )
}
log.d("Class ${cn.name} Class policy: $classPolicy")
@@ -120,7 +117,8 @@ class ImplicitOutputFilter(
// For synthetic methods (such as lambdas), let's just inherit the class's
// policy.
return memberPolicy.withReason(classPolicy.reason).wrapReason(
- "is-synthetic-method")
+ "is-synthetic-method"
+ )
}
}
}
@@ -129,8 +127,8 @@ class ImplicitOutputFilter(
}
override fun getPolicyForField(
- className: String,
- fieldName: String
+ className: String,
+ fieldName: String
): FilterPolicyWithReason {
val fallback = super.getPolicyForField(className, fieldName)
@@ -161,4 +159,4 @@ class ImplicitOutputFilter(
return fallback
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index fc885d6f463b..59da3da99ea5 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -28,10 +28,12 @@ class InMemoryOutputFilter(
private val classes: ClassNodes,
fallback: OutputFilter,
) : DelegatingFilter(fallback) {
- private val mPolicies: MutableMap<String, FilterPolicyWithReason> = mutableMapOf()
- private val mRenames: MutableMap<String, String> = mutableMapOf()
- private val mRedirectionClasses: MutableMap<String, String> = mutableMapOf()
- private val mClassLoadHooks: MutableMap<String, String> = mutableMapOf()
+ private val mPolicies = mutableMapOf<String, FilterPolicyWithReason>()
+ private val mRenames = mutableMapOf<String, String>()
+ private val mRedirectionClasses = mutableMapOf<String, String>()
+ private val mClassLoadHooks = mutableMapOf<String, String>()
+ private val mMethodCallReplaceSpecs = mutableListOf<MethodCallReplaceSpec>()
+ private val mTypeRenameSpecs = mutableListOf<TypeRenameSpec>()
private fun getClassKey(className: String): String {
return className.toHumanReadableClassName()
@@ -45,10 +47,6 @@ class InMemoryOutputFilter(
return getClassKey(className) + "." + methodName + ";" + signature
}
- override fun getPolicyForClass(className: String): FilterPolicyWithReason {
- return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
- }
-
private fun checkClass(className: String) {
if (classes.findClass(className) == null) {
log.w("Unknown class $className")
@@ -74,6 +72,10 @@ class InMemoryOutputFilter(
}
}
+ override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+ return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
+ }
+
fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) {
checkClass(className)
mPolicies[getClassKey(className)] = policy
@@ -81,7 +83,7 @@ class InMemoryOutputFilter(
override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
return mPolicies[getFieldKey(className, fieldName)]
- ?: super.getPolicyForField(className, fieldName)
+ ?: super.getPolicyForField(className, fieldName)
}
fun setPolicyForField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
@@ -90,21 +92,21 @@ class InMemoryOutputFilter(
}
override fun getPolicyForMethod(
- className: String,
- methodName: String,
- descriptor: String,
- ): FilterPolicyWithReason {
+ className: String,
+ methodName: String,
+ descriptor: String,
+ ): FilterPolicyWithReason {
return mPolicies[getMethodKey(className, methodName, descriptor)]
?: mPolicies[getMethodKey(className, methodName, "*")]
?: super.getPolicyForMethod(className, methodName, descriptor)
}
fun setPolicyForMethod(
- className: String,
- methodName: String,
- descriptor: String,
- policy: FilterPolicyWithReason,
- ) {
+ className: String,
+ methodName: String,
+ descriptor: String,
+ policy: FilterPolicyWithReason,
+ ) {
checkMethod(className, methodName, descriptor)
mPolicies[getMethodKey(className, methodName, descriptor)] = policy
}
@@ -123,7 +125,7 @@ class InMemoryOutputFilter(
override fun getRedirectionClass(className: String): String? {
return mRedirectionClasses[getClassKey(className)]
- ?: super.getRedirectionClass(className)
+ ?: super.getRedirectionClass(className)
}
fun setRedirectionClass(from: String, to: String) {
@@ -135,11 +137,52 @@ class InMemoryOutputFilter(
}
override fun getClassLoadHooks(className: String): List<String> {
- return addNonNullElement(super.getClassLoadHooks(className),
- mClassLoadHooks[getClassKey(className)])
+ return addNonNullElement(
+ super.getClassLoadHooks(className),
+ mClassLoadHooks[getClassKey(className)]
+ )
}
fun setClassLoadHook(className: String, methodName: String) {
mClassLoadHooks[getClassKey(className)] = methodName.toHumanReadableMethodName()
}
+
+ override fun hasAnyMethodCallReplace(): Boolean {
+ return mMethodCallReplaceSpecs.isNotEmpty() || super.hasAnyMethodCallReplace()
+ }
+
+ override fun getMethodCallReplaceTo(
+ className: String,
+ methodName: String,
+ descriptor: String,
+ ): MethodReplaceTarget? {
+ // Maybe use 'Tri' if we end up having too many replacements.
+ mMethodCallReplaceSpecs.forEach {
+ if (className == it.fromClass &&
+ methodName == it.fromMethod
+ ) {
+ if (it.fromDescriptor == "*" || descriptor == it.fromDescriptor) {
+ return MethodReplaceTarget(it.toClass, it.toMethod)
+ }
+ }
+ }
+ return super.getMethodCallReplaceTo(className, methodName, descriptor)
+ }
+
+ fun setMethodCallReplaceSpec(spec: MethodCallReplaceSpec) {
+ mMethodCallReplaceSpecs.add(spec)
+ }
+
+ override fun remapType(className: String): String? {
+ mTypeRenameSpecs.forEach {
+ if (it.typeInternalNamePattern.matcher(className).matches()) {
+ return it.typeInternalNamePrefix + className
+ }
+ }
+ return super.remapType(className)
+ }
+
+ fun setRemapTypeSpec(spec: TypeRenameSpec) {
+ mTypeRenameSpecs.add(spec)
+ }
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt
index 00e7d77fa6e7..00e7d77fa6e7 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/OutputFilter.kt
index f99ce906240a..c47bb302920f 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/OutputFilter.kt
@@ -41,10 +41,10 @@ abstract class OutputFilter {
abstract fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason
abstract fun getPolicyForMethod(
- className: String,
- methodName: String,
- descriptor: String,
- ): FilterPolicyWithReason
+ className: String,
+ methodName: String,
+ descriptor: String,
+ ): FilterPolicyWithReason
/**
* If a given method is a substitute-from method, return the substitute-to method name.
@@ -108,8 +108,6 @@ abstract class OutputFilter {
* If a method call should be forwarded to another method, return the target's class / method.
*/
open fun getMethodCallReplaceTo(
- callerClassName: String,
- callerMethodName: String,
className: String,
methodName: String,
descriptor: String,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/PackageFilter.kt
index c67e6714d4c2..c67e6714d4c2 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/PackageFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SanitizationFilter.kt
index 4375c6500b62..4375c6500b62 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SanitizationFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SubclassFilter.kt
index fd7474b55fa6..fd7474b55fa6 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SubclassFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index dd353e9caeff..97fc35302528 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -22,13 +22,13 @@ import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.log
import com.android.hoststubgen.normalizeTextLine
+import com.android.hoststubgen.utils.FileOrResource
import com.android.hoststubgen.whitespaceRegex
-import org.objectweb.asm.tree.ClassNode
import java.io.BufferedReader
-import java.io.FileReader
import java.io.PrintWriter
import java.io.Reader
import java.util.regex.Pattern
+import org.objectweb.asm.tree.ClassNode
/**
* Print a class node as a "keep" policy.
@@ -58,6 +58,23 @@ enum class SpecialClass {
RFile,
}
+data class MethodCallReplaceSpec(
+ val fromClass: String,
+ val fromMethod: String,
+ val fromDescriptor: String,
+ val toClass: String,
+ val toMethod: String,
+)
+
+/**
+ * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix]
+ * to it.
+ */
+data class TypeRenameSpec(
+ val typeInternalNamePattern: Pattern,
+ val typeInternalNamePrefix: String,
+)
+
/**
* This receives [TextFileFilterPolicyBuilder] parsing result.
*/
@@ -99,7 +116,7 @@ interface PolicyFileProcessor {
className: String,
methodName: String,
methodDesc: String,
- replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ replaceSpec: MethodCallReplaceSpec,
)
}
@@ -116,9 +133,6 @@ class TextFileFilterPolicyBuilder(
private var featureFlagsPolicy: FilterPolicyWithReason? = null
private var syspropsPolicy: FilterPolicyWithReason? = null
private var rFilePolicy: FilterPolicyWithReason? = null
- private val typeRenameSpec = mutableListOf<TextFilePolicyRemapperFilter.TypeRenameSpec>()
- private val methodReplaceSpec =
- mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()
/**
* Fields for a filter chain used for "partial allowlisting", which are used by
@@ -126,47 +140,34 @@ class TextFileFilterPolicyBuilder(
*/
private val annotationAllowedInMemoryFilter: InMemoryOutputFilter
val annotationAllowedMembersFilter: OutputFilter
+ get() = annotationAllowedInMemoryFilter
private val annotationAllowedPolicy = FilterPolicy.AnnotationAllowed.withReason(FILTER_REASON)
init {
// Create a filter that checks "partial allowlisting".
- var aaf: OutputFilter = ConstantFilter(FilterPolicy.Remove, "default disallowed")
-
- aaf = InMemoryOutputFilter(classes, aaf)
- annotationAllowedInMemoryFilter = aaf
-
- annotationAllowedMembersFilter = annotationAllowedInMemoryFilter
+ val filter = ConstantFilter(FilterPolicy.Remove, "default disallowed")
+ annotationAllowedInMemoryFilter = InMemoryOutputFilter(classes, filter)
}
/**
* Parse a given policy file. This method can be called multiple times to read from
* multiple files. To get the resulting filter, use [createOutputFilter]
*/
- fun parse(file: String) {
+ fun parse(file: FileOrResource) {
// We may parse multiple files, but we reuse the same parser, because the parser
// will make sure there'll be no dupplicating "special class" policies.
- parser.parse(FileReader(file), file, Processor())
+ parser.parse(file.open(), file.path, Processor())
}
/**
* Generate the resulting [OutputFilter].
*/
fun createOutputFilter(): OutputFilter {
- var ret: OutputFilter = imf
- if (typeRenameSpec.isNotEmpty()) {
- ret = TextFilePolicyRemapperFilter(typeRenameSpec, ret)
- }
- if (methodReplaceSpec.isNotEmpty()) {
- ret = TextFilePolicyMethodReplaceFilter(methodReplaceSpec, classes, ret)
- }
-
// Wrap the in-memory-filter with AHF.
- ret = AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, ret
+ return AndroidHeuristicsFilter(
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf
)
-
- return ret
}
private inner class Processor : PolicyFileProcessor {
@@ -180,9 +181,7 @@ class TextFileFilterPolicyBuilder(
}
override fun onRename(pattern: Pattern, prefix: String) {
- typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
- pattern, prefix
- )
+ imf.setRemapTypeSpec(TypeRenameSpec(pattern, prefix))
}
override fun onClassStart(className: String) {
@@ -284,12 +283,12 @@ class TextFileFilterPolicyBuilder(
className: String,
methodName: String,
methodDesc: String,
- replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ replaceSpec: MethodCallReplaceSpec,
) {
// Keep the source method, because the target method may call it.
imf.setPolicyForMethod(className, methodName, methodDesc,
FilterPolicy.Keep.withReason(FILTER_REASON))
- methodReplaceSpec.add(replaceSpec)
+ imf.setMethodCallReplaceSpec(replaceSpec)
}
}
}
@@ -630,13 +629,13 @@ class TextFileFilterPolicyParser {
if (classAndMethod != null) {
// If the substitution target contains a ".", then
// it's a method call redirect.
- val spec = TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
- currentClassName!!.toJvmClassName(),
- methodName,
- signature,
- classAndMethod.first.toJvmClassName(),
- classAndMethod.second,
- )
+ val spec = MethodCallReplaceSpec(
+ className.toJvmClassName(),
+ methodName,
+ signature,
+ classAndMethod.first.toJvmClassName(),
+ classAndMethod.second,
+ )
processor.onMethodOutClassReplace(
className,
methodName,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassPredicate.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassPredicate.kt
index 4c53bc8fba97..4c53bc8fba97 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassPredicate.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassPredicate.kt
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt
new file mode 100644
index 000000000000..d0869929edfb
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.utils
+
+import com.android.hoststubgen.ArgumentsException
+import com.android.hoststubgen.InputFileNotFoundException
+import com.android.hoststubgen.JarResourceNotFoundException
+import com.android.hoststubgen.log
+import com.android.hoststubgen.normalizeTextLine
+import java.io.File
+import java.io.FileReader
+import java.io.InputStreamReader
+import java.io.Reader
+
+const val JAR_RESOURCE_PREFIX = "jar:"
+
+/**
+ * Base class for parsing arguments from commandline.
+ */
+abstract class BaseOptions {
+ /**
+ * Parse all arguments.
+ *
+ * This method should remain final. For customization in subclasses, override [parseOption].
+ */
+ fun parseArgs(args: List<String>) {
+ val ai = ArgIterator.withAtFiles(args)
+ while (true) {
+ val arg = ai.nextArgOptional() ?: break
+
+ if (log.maybeHandleCommandLineArg(arg) { ai.nextArgRequired(arg) }) {
+ continue
+ }
+ try {
+ if (!parseOption(arg, ai)) {
+ throw ArgumentsException("Unknown option: $arg")
+ }
+ } catch (e: SetOnce.SetMoreThanOnceException) {
+ throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+ }
+ }
+
+ checkArgs()
+ }
+
+ /**
+ * Print out all fields in this class.
+ *
+ * This method should remain final. For customization in subclasses, override [dumpFields].
+ */
+ final override fun toString(): String {
+ val fields = dumpFields().prependIndent(" ")
+ return "${this::class.simpleName} {\n$fields\n}"
+ }
+
+ /**
+ * Check whether the parsed options are in a correct state.
+ *
+ * This method is called as the last step in [parseArgs].
+ */
+ open fun checkArgs() {}
+
+ /**
+ * Parse a single option. Return true if the option is accepted, otherwise return false.
+ *
+ * Subclasses override/extend this method to support more options.
+ */
+ abstract fun parseOption(option: String, args: ArgIterator): Boolean
+
+ abstract fun dumpFields(): String
+}
+
+class ArgIterator(
+ private val args: List<String>,
+ private var currentIndex: Int = -1
+) {
+ val current: String
+ get() = args[currentIndex]
+
+ /**
+ * Get the next argument, or [null] if there's no more arguments.
+ */
+ fun nextArgOptional(): String? {
+ if ((currentIndex + 1) >= args.size) {
+ return null
+ }
+ return args[++currentIndex]
+ }
+
+ /**
+ * Get the next argument, or throw if
+ */
+ fun nextArgRequired(argName: String): String {
+ nextArgOptional().let {
+ if (it == null) {
+ throw ArgumentsException("Missing parameter for option $argName")
+ }
+ if (it.isEmpty()) {
+ throw ArgumentsException("Parameter can't be empty for option $argName")
+ }
+ return it
+ }
+ }
+
+ companion object {
+ fun withAtFiles(args: List<String>): ArgIterator {
+ val expanded = mutableListOf<String>()
+ expandAtFiles(args.asSequence(), expanded)
+ return ArgIterator(expanded)
+ }
+
+ /**
+ * Scan the arguments, and if any of them starts with an `@`, then load from the file
+ * and use its content as arguments.
+ *
+ * In order to pass an argument that starts with an '@', use '@@' instead.
+ *
+ * In this file, each line is treated as a single argument.
+ *
+ * The file can contain '#' as comments.
+ */
+ private fun expandAtFiles(args: Sequence<String>, out: MutableList<String>) {
+ args.forEach { arg ->
+ if (arg.startsWith("@@")) {
+ out.add(arg.substring(1))
+ return@forEach
+ } else if (!arg.startsWith('@')) {
+ out.add(arg)
+ return@forEach
+ }
+
+ // Read from the file, and add each line to the result.
+ val file = FileOrResource(arg.substring(1))
+
+ log.v("Expanding options file ${file.path}")
+
+ val fileArgs = file
+ .open()
+ .buffered()
+ .lineSequence()
+ .map(::normalizeTextLine)
+ .filter(CharSequence::isNotEmpty)
+
+ expandAtFiles(fileArgs, out)
+ }
+ }
+ }
+}
+
+/**
+ * A single value that can only set once.
+ */
+open class SetOnce<T>(private var value: T) {
+ class SetMoreThanOnceException : Exception()
+
+ private var set = false
+
+ fun set(v: T): T {
+ if (set) {
+ throw SetMoreThanOnceException()
+ }
+ if (v == null) {
+ throw NullPointerException("This shouldn't happen")
+ }
+ set = true
+ value = v
+ return v
+ }
+
+ val get: T
+ get() = this.value
+
+ val isSet: Boolean
+ get() = this.set
+
+ fun <R> ifSet(block: (T & Any) -> R): R? {
+ if (isSet) {
+ return block(value!!)
+ }
+ return null
+ }
+
+ override fun toString(): String {
+ return "$value"
+ }
+}
+
+class IntSetOnce(value: Int) : SetOnce<Int>(value) {
+ fun set(v: String): Int {
+ try {
+ return this.set(v.toInt())
+ } catch (e: NumberFormatException) {
+ throw ArgumentsException("Invalid integer $v")
+ }
+ }
+}
+
+/**
+ * A path either points to a file in filesystem, or an entry in the JAR.
+ */
+class FileOrResource(val path: String) {
+ init {
+ path.ensureFileExists()
+ }
+
+ /**
+ * Either read from filesystem, or read from JAR resources.
+ */
+ fun open(): Reader {
+ return if (path.startsWith(JAR_RESOURCE_PREFIX)) {
+ val path = path.removePrefix(JAR_RESOURCE_PREFIX)
+ InputStreamReader(this::class.java.classLoader.getResourceAsStream(path)!!)
+ } else {
+ FileReader(path)
+ }
+ }
+}
+
+fun String.ensureFileExists(): String {
+ if (this.startsWith(JAR_RESOURCE_PREFIX)) {
+ val cl = FileOrResource::class.java.classLoader
+ val path = this.removePrefix(JAR_RESOURCE_PREFIX)
+ if (cl.getResource(path) == null) {
+ throw JarResourceNotFoundException(path)
+ }
+ } else if (!File(this).exists()) {
+ throw InputFileNotFoundException(this)
+ }
+ return this
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/Trie.kt
index 1b3d79cddb8e..1b3d79cddb8e 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/Trie.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.kt
index a08d1d605949..769b769d7a20 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -17,7 +17,6 @@ package com.android.hoststubgen.visitors
import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.HostStubGenStats
-import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
@@ -26,13 +25,10 @@ import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.log
-import java.io.PrintWriter
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
-import org.objectweb.asm.commons.ClassRemapper
-import org.objectweb.asm.util.TraceClassVisitor
const val OPCODE_VERSION = Opcodes.ASM9
@@ -49,8 +45,6 @@ abstract class BaseAdapter(
data class Options(
val errors: HostStubGenErrors,
val stats: HostStubGenStats?,
- val enablePreTrace: Boolean,
- val enablePostTrace: Boolean,
val deleteClassFinals: Boolean,
val deleteMethodFinals: Boolean,
// We don't remove finals from fields, because final fields have a stronger memory
@@ -253,50 +247,4 @@ abstract class BaseAdapter(
substituted: Boolean,
superVisitor: MethodVisitor?,
): MethodVisitor?
-
- companion object {
- fun getVisitor(
- classInternalName: String,
- classes: ClassNodes,
- nextVisitor: ClassVisitor,
- filter: OutputFilter,
- packageRedirector: PackageRedirectRemapper,
- options: Options,
- ): ClassVisitor {
- var next = nextVisitor
-
- val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose))
-
- // Inject TraceClassVisitor for debugging.
- if (options.enablePostTrace) {
- next = TraceClassVisitor(next, verbosePrinter)
- }
-
- // Handle --package-redirect
- if (!packageRedirector.isEmpty) {
- // Don't apply the remapper on redirect-from classes.
- // Otherwise, if the target jar actually contains the "from" classes (which
- // may or may not be the case) they'd be renamed.
- // But we update all references in other places, so, a method call to a "from" class
- // would be replaced with the "to" class. All type references (e.g. variable types)
- // will be updated too.
- if (!packageRedirector.isTarget(classInternalName)) {
- next = ClassRemapper(next, packageRedirector)
- } else {
- log.v(
- "Class $classInternalName is a redirect-from class, not applying" +
- " --package-redirect"
- )
- }
- }
-
- next = ImplGeneratingAdapter(classes, next, filter, options)
-
- // Inject TraceClassVisitor for debugging.
- if (options.enablePreTrace) {
- next = TraceClassVisitor(next, verbosePrinter)
- }
- return next
- }
- }
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
index 55d0c0e555f1..55d0c0e555f1 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/Helper.kt
index dc4f26bdda34..dc4f26bdda34 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/Helper.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index b8a357668c2b..617385ad438e 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -396,7 +396,7 @@ class ImplGeneratingAdapter(
}
val to = filter.getMethodCallReplaceTo(
- currentClassName, callerMethodName, owner, name, descriptor
+ owner, name, descriptor
)
if (to == null
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt
index e90ecd7ef678..e90ecd7ef678 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 7e294ed652d3..333540573364 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -17,36 +17,15 @@ package com.android.hoststubgen
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.dumper.ApiDumper
-import com.android.hoststubgen.filters.AnnotationBasedFilter
-import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
-import com.android.hoststubgen.filters.ConstantFilter
-import com.android.hoststubgen.filters.DefaultHookInjectingFilter
import com.android.hoststubgen.filters.FilterPolicy
-import com.android.hoststubgen.filters.FilterRemapper
-import com.android.hoststubgen.filters.ImplicitOutputFilter
-import com.android.hoststubgen.filters.KeepNativeFilter
-import com.android.hoststubgen.filters.OutputFilter
-import com.android.hoststubgen.filters.SanitizationFilter
-import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.filters.printAsTextPolicy
-import com.android.hoststubgen.utils.ClassPredicate
-import com.android.hoststubgen.visitors.BaseAdapter
-import com.android.hoststubgen.visitors.PackageRedirectRemapper
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.FileOutputStream
-import java.io.InputStream
-import java.io.OutputStream
import java.io.PrintWriter
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.ClassVisitor
-import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.commons.ClassRemapper
-import org.objectweb.asm.commons.Remapper
-import org.objectweb.asm.util.CheckClassAdapter
/**
* Actual main class.
@@ -76,21 +55,15 @@ class HostStubGen(val options: HostStubGenOptions) {
}
}
- // Build the filters.
- val filter = buildFilter(errors, allClasses, options)
-
- val filterRemapper = FilterRemapper(filter)
+ // Build the class processor
+ val processor = HostStubGenClassProcessor(options, allClasses, errors, stats)
// Transform the jar.
convert(
options.inJar.get,
options.outJar.get,
- filter,
+ processor,
options.enableClassChecker.get,
- allClasses,
- errors,
- stats,
- filterRemapper,
options.numShards.get,
options.shard.get,
)
@@ -106,109 +79,20 @@ class HostStubGen(val options: HostStubGenOptions) {
PrintWriter(it).use { pw ->
// TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
// framework-minus-apex.jar so that we can dump inherited methods from it.
- ApiDumper(pw, allClasses, null, filter).dump()
+ ApiDumper(pw, allClasses, null, processor.filter).dump()
}
}
}
}
/**
- * Build the filter, which decides what classes/methods/fields should be put in stub or impl
- * jars, and "how". (e.g. with substitution?)
- */
- private fun buildFilter(
- errors: HostStubGenErrors,
- allClasses: ClassNodes,
- options: HostStubGenOptions,
- ): OutputFilter {
- // We build a "chain" of multiple filters here.
- //
- // The filters are build in from "inside", meaning the first filter created here is
- // the last filter used, so it has the least precedence.
- //
- // So, for example, the "remove" annotation, which is handled by AnnotationBasedFilter,
- // can override a class-wide annotation, which is handled by
- // ClassWidePolicyPropagatingFilter, and any annotations can be overridden by the
- // text-file based filter, which is handled by parseTextFilterPolicyFile.
-
- // The first filter is for the default policy from the command line options.
- var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options")
-
- // Next, we build a filter that preserves all native methods by default
- filter = KeepNativeFilter(allClasses, filter)
-
- // Next, we need a filter that resolves "class-wide" policies.
- // This is used when a member (methods, fields, nested classes) don't get any polices
- // from upper filters. e.g. when a method has no annotations, then this filter will apply
- // the class-wide policy, if any. (if not, we'll fall back to the above filter.)
- filter = ClassWidePolicyPropagatingFilter(allClasses, filter)
-
- // Inject default hooks from options.
- filter = DefaultHookInjectingFilter(
- allClasses,
- options.defaultClassLoadHook.get,
- options.defaultMethodCallHook.get,
- filter
- )
-
- val annotationAllowedPredicate = options.annotationAllowedClassesFile.get.let { file ->
- if (file == null) {
- ClassPredicate.newConstantPredicate(true) // Allow all classes
- } else {
- ClassPredicate.loadFromFile(file, false)
- }
- }
-
- // Next, Java annotation based filter.
- val annotFilter = AnnotationBasedFilter(
- errors,
- allClasses,
- options.keepAnnotations,
- options.keepClassAnnotations,
- options.throwAnnotations,
- options.removeAnnotations,
- options.ignoreAnnotations,
- options.substituteAnnotations,
- options.redirectAnnotations,
- options.redirectionClassAnnotations,
- options.classLoadHookAnnotations,
- options.partiallyAllowedAnnotations,
- options.keepStaticInitializerAnnotations,
- annotationAllowedPredicate,
- filter
- )
- filter = annotFilter
-
- // Next, "text based" filter, which allows to override polices without touching
- // the target code.
- if (options.policyOverrideFiles.isNotEmpty()) {
- val builder = TextFileFilterPolicyBuilder(allClasses, filter)
- options.policyOverrideFiles.forEach(builder::parse)
- filter = builder.createOutputFilter()
- annotFilter.annotationAllowedMembers = builder.annotationAllowedMembersFilter
- }
-
- // Apply the implicit filter.
- filter = ImplicitOutputFilter(errors, allClasses, filter)
-
- // Add a final sanitization step.
- filter = SanitizationFilter(errors, allClasses, filter)
-
- return filter
- }
-
- /**
* Convert a JAR file into "stub" and "impl" JAR files.
*/
private fun convert(
inJar: String,
outJar: String?,
- filter: OutputFilter,
+ processor: HostStubGenClassProcessor,
enableChecker: Boolean,
- classes: ClassNodes,
- errors: HostStubGenErrors,
- stats: HostStubGenStats,
- remapper: Remapper?,
numShards: Int,
shard: Int
) {
@@ -216,8 +100,6 @@ class HostStubGen(val options: HostStubGenOptions) {
log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
log.iTime("Transforming jar") {
- val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
-
var itemIndex = 0
var numItemsProcessed = 0
var numItems = -1 // == Unknown
@@ -240,11 +122,7 @@ class HostStubGen(val options: HostStubGenOptions) {
if (!inShard) {
continue
}
- convertSingleEntry(
- inZip, entry, outStream, filter,
- packageRedirector, remapper, enableChecker,
- classes, errors, stats
- )
+ convertSingleEntry(inZip, entry, outStream, processor)
numItemsProcessed++
}
log.i("Converted all entries.")
@@ -270,13 +148,7 @@ class HostStubGen(val options: HostStubGenOptions) {
inZip: ZipFile,
entry: ZipEntry,
outStream: ZipOutputStream?,
- filter: OutputFilter,
- packageRedirector: PackageRedirectRemapper,
- remapper: Remapper?,
- enableChecker: Boolean,
- classes: ClassNodes,
- errors: HostStubGenErrors,
- stats: HostStubGenStats
+ processor: HostStubGenClassProcessor
) {
log.d("Entry: %s", entry.name)
log.withIndent {
@@ -289,10 +161,7 @@ class HostStubGen(val options: HostStubGenOptions) {
// If it's a class, convert it.
if (name.endsWith(".class")) {
- processSingleClass(
- inZip, entry, outStream, filter, packageRedirector,
- remapper, enableChecker, classes, errors, stats
- )
+ processSingleClass(inZip, entry, outStream, processor)
return
}
@@ -332,29 +201,23 @@ class HostStubGen(val options: HostStubGenOptions) {
}
/**
- * Convert a single class to "stub" and "impl".
+ * Convert a single class.
*/
private fun processSingleClass(
inZip: ZipFile,
entry: ZipEntry,
outStream: ZipOutputStream?,
- filter: OutputFilter,
- packageRedirector: PackageRedirectRemapper,
- remapper: Remapper?,
- enableChecker: Boolean,
- classes: ClassNodes,
- errors: HostStubGenErrors,
- stats: HostStubGenStats
+ processor: HostStubGenClassProcessor
) {
val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
- val classPolicy = filter.getPolicyForClass(classInternalName)
+ val classPolicy = processor.filter.getPolicyForClass(classInternalName)
if (classPolicy.policy == FilterPolicy.Remove) {
log.d("Removing class: %s %s", classInternalName, classPolicy)
return
}
// If we're applying a remapper, we need to rename the file too.
var newName = entry.name
- remapper?.mapType(classInternalName)?.let { remappedName ->
+ processor.remapper.mapType(classInternalName)?.let { remappedName ->
if (remappedName != classInternalName) {
log.d("Renaming class file: %s -> %s", classInternalName, remappedName)
newName = "$remappedName.class"
@@ -367,64 +230,11 @@ class HostStubGen(val options: HostStubGenOptions) {
BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
val newEntry = ZipEntry(newName)
outStream.putNextEntry(newEntry)
- convertClass(
- classInternalName, bis,
- outStream, filter, packageRedirector, remapper,
- enableChecker, classes, errors, stats
- )
+ val classBytecode = bis.readAllBytes()
+ outStream.write(processor.processClassBytecode(classBytecode))
outStream.closeEntry()
}
}
}
}
-
- /**
- * Convert a single class to either "stub" or "impl".
- */
- private fun convertClass(
- classInternalName: String,
- input: InputStream,
- out: OutputStream,
- filter: OutputFilter,
- packageRedirector: PackageRedirectRemapper,
- remapper: Remapper?,
- enableChecker: Boolean,
- classes: ClassNodes,
- errors: HostStubGenErrors,
- stats: HostStubGenStats?
- ) {
- val cr = ClassReader(input)
-
- // COMPUTE_FRAMES wouldn't be happy if code uses
- val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES
- val cw = ClassWriter(flags)
-
- // Connect to the class writer
- var outVisitor: ClassVisitor = cw
- if (enableChecker) {
- outVisitor = CheckClassAdapter(outVisitor)
- }
-
- // Remapping should happen at the end.
- remapper?.let {
- outVisitor = ClassRemapper(outVisitor, remapper)
- }
-
- val visitorOptions = BaseAdapter.Options(
- errors = errors,
- stats = stats,
- enablePreTrace = options.enablePreTrace.get,
- enablePostTrace = options.enablePostTrace.get,
- deleteClassFinals = options.deleteFinals.get,
- deleteMethodFinals = options.deleteFinals.get,
- )
- outVisitor = BaseAdapter.getVisitor(
- classInternalName, classes, outVisitor, filter,
- packageRedirector, visitorOptions
- )
-
- cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
- val data = cw.toByteArray()
- out.write(data)
- }
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 85064661cd2b..4ba8c5c50059 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -17,8 +17,6 @@
package com.android.hoststubgen
-import java.io.PrintWriter
-
/**
* Entry point.
*/
@@ -26,10 +24,10 @@ fun main(args: Array<String>) {
executableName = "HostStubGen"
runMainWithBoilerplate {
// Parse the command line arguments.
- var clanupOnError = false
+ var cleanupOnError = false
try {
- val options = HostStubGenOptions.parseArgs(args)
- clanupOnError = options.cleanUpOnError.get
+ val options = HostStubGenOptions().apply { parseArgs(args.asList()) }
+ cleanupOnError = options.cleanUpOnError.get
log.v("$executableName started")
log.v("Options: $options")
@@ -37,30 +35,10 @@ fun main(args: Array<String>) {
// Run.
HostStubGen(options).run()
} catch (e: Throwable) {
- if (clanupOnError) {
+ if (cleanupOnError) {
TODO("Remove output jars here")
}
throw e
}
}
}
-
-inline fun runMainWithBoilerplate(realMain: () -> Unit) {
- var success = false
-
- try {
- realMain()
-
- success = true
- } catch (e: Throwable) {
- log.e("$executableName: Error: ${e.message}")
- if (e !is UserErrorException) {
- e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
- }
- } finally {
- log.i("$executableName finished")
- log.flush()
- }
-
- System.exit(if (success) 0 else 1 )
-}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 1ab88d24ab28..d9cc54aebf51 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -15,385 +15,103 @@
*/
package com.android.hoststubgen
-import com.android.hoststubgen.filters.FilterPolicy
-import java.io.BufferedReader
-import java.io.FileReader
-
-/**
- * A single value that can only set once.
- */
-open class SetOnce<T>(private var value: T) {
- class SetMoreThanOnceException : Exception()
-
- private var set = false
-
- fun set(v: T): T {
- if (set) {
- throw SetMoreThanOnceException()
- }
- if (v == null) {
- throw NullPointerException("This shouldn't happen")
- }
- set = true
- value = v
- return v
- }
-
- val get: T
- get() = this.value
-
- val isSet: Boolean
- get() = this.set
-
- fun <R> ifSet(block: (T & Any) -> R): R? {
- if (isSet) {
- return block(value!!)
- }
- return null
- }
-
- override fun toString(): String {
- return "$value"
- }
-}
-
-class IntSetOnce(value: Int) : SetOnce<Int>(value) {
- fun set(v: String): Int {
- try {
- return this.set(v.toInt())
- } catch (e: NumberFormatException) {
- throw ArgumentsException("Invalid integer $v")
- }
- }
-}
+import com.android.hoststubgen.utils.ArgIterator
+import com.android.hoststubgen.utils.IntSetOnce
+import com.android.hoststubgen.utils.SetOnce
+import com.android.hoststubgen.utils.ensureFileExists
/**
* Options that can be set from command line arguments.
*/
class HostStubGenOptions(
- /** Input jar file*/
- var inJar: SetOnce<String> = SetOnce(""),
-
- /** Output jar file */
- var outJar: SetOnce<String?> = SetOnce(null),
-
- var inputJarDumpFile: SetOnce<String?> = SetOnce(null),
-
- var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null),
-
- var keepAnnotations: MutableSet<String> = mutableSetOf(),
- var throwAnnotations: MutableSet<String> = mutableSetOf(),
- var removeAnnotations: MutableSet<String> = mutableSetOf(),
- var ignoreAnnotations: MutableSet<String> = mutableSetOf(),
- var keepClassAnnotations: MutableSet<String> = mutableSetOf(),
- var partiallyAllowedAnnotations: MutableSet<String> = mutableSetOf(),
- var redirectAnnotations: MutableSet<String> = mutableSetOf(),
-
- var substituteAnnotations: MutableSet<String> = mutableSetOf(),
- var redirectionClassAnnotations: MutableSet<String> = mutableSetOf(),
- var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(),
- var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(),
+ /** Input jar file*/
+ var inJar: SetOnce<String> = SetOnce(""),
- var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
+ /** Output jar file */
+ var outJar: SetOnce<String?> = SetOnce(null),
- var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null),
+ var inputJarDumpFile: SetOnce<String?> = SetOnce(null),
- var defaultClassLoadHook: SetOnce<String?> = SetOnce(null),
- var defaultMethodCallHook: SetOnce<String?> = SetOnce(null),
+ var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null),
- var policyOverrideFiles: MutableList<String> = mutableListOf(),
+ var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
- var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
+ var statsFile: SetOnce<String?> = SetOnce(null),
- var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
+ var apiListFile: SetOnce<String?> = SetOnce(null),
- var deleteFinals: SetOnce<Boolean> = SetOnce(false),
+ var numShards: IntSetOnce = IntSetOnce(1),
+ var shard: IntSetOnce = IntSetOnce(0),
+) : HostStubGenClassProcessorOptions() {
- var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
- var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
- var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
-
- var statsFile: SetOnce<String?> = SetOnce(null),
-
- var apiListFile: SetOnce<String?> = SetOnce(null),
-
- var numShards: IntSetOnce = IntSetOnce(1),
- var shard: IntSetOnce = IntSetOnce(0),
-) {
- companion object {
-
- private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> {
- val colon = fromColonTo.indexOf(':')
- if ((colon < 1) || (colon + 1 >= fromColonTo.length)) {
- throw ArgumentsException("--package-redirect must be a colon-separated string")
- }
- // TODO check for duplicates
- return Pair(fromColonTo.substring(0, colon), fromColonTo.substring(colon + 1))
+ override fun checkArgs() {
+ if (!inJar.isSet) {
+ throw ArgumentsException("Required option missing: --in-jar")
+ }
+ if (!outJar.isSet) {
+ log.w("--out-jar is not set. $executableName will not generate jar files.")
+ }
+ if (numShards.isSet != shard.isSet) {
+ throw ArgumentsException("--num-shards and --shard-index must be used together")
}
- fun parseArgs(args: Array<String>): HostStubGenOptions {
- val ret = HostStubGenOptions()
-
- val ai = ArgIterator.withAtFiles(args)
-
- var allAnnotations = mutableSetOf<String>()
-
- fun ensureUniqueAnnotation(name: String): String {
- if (!allAnnotations.add(name)) {
- throw DuplicateAnnotationException(ai.current)
- }
- return name
+ if (numShards.isSet) {
+ if (shard.get >= numShards.get) {
+ throw ArgumentsException("--shard-index must be smaller than --num-shards")
}
+ }
+ }
- while (true) {
- val arg = ai.nextArgOptional() ?: break
-
- // Define some shorthands...
- fun nextArg(): String = ai.nextArgRequired(arg)
- fun MutableSet<String>.addUniqueAnnotationArg(): String =
- nextArg().also { this += ensureUniqueAnnotation(it) }
-
- if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
- continue
- }
- try {
- when (arg) {
- // TODO: Write help
- "-h", "--help" -> TODO("Help is not implemented yet")
-
- "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
- // We support both arguments because some AOSP dependencies
- // still use the old argument
- "--out-jar", "--out-impl-jar" -> ret.outJar.set(nextArg())
-
- "--policy-override-file" ->
- ret.policyOverrideFiles.add(nextArg().ensureFileExists())
-
- "--clean-up-on-error" -> ret.cleanUpOnError.set(true)
- "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false)
-
- "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove)
- "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw)
- "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep)
-
- "--keep-annotation" ->
- ret.keepAnnotations.addUniqueAnnotationArg()
-
- "--keep-class-annotation" ->
- ret.keepClassAnnotations.addUniqueAnnotationArg()
-
- "--partially-allowed-annotation" ->
- ret.partiallyAllowedAnnotations.addUniqueAnnotationArg()
-
- "--throw-annotation" ->
- ret.throwAnnotations.addUniqueAnnotationArg()
-
- "--remove-annotation" ->
- ret.removeAnnotations.addUniqueAnnotationArg()
-
- "--ignore-annotation" ->
- ret.ignoreAnnotations.addUniqueAnnotationArg()
-
- "--substitute-annotation" ->
- ret.substituteAnnotations.addUniqueAnnotationArg()
-
- "--redirect-annotation" ->
- ret.redirectAnnotations.addUniqueAnnotationArg()
-
- "--redirection-class-annotation" ->
- ret.redirectionClassAnnotations.addUniqueAnnotationArg()
-
- "--class-load-hook-annotation" ->
- ret.classLoadHookAnnotations.addUniqueAnnotationArg()
-
- "--keep-static-initializer-annotation" ->
- ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg()
-
- "--package-redirect" ->
- ret.packageRedirects += parsePackageRedirect(nextArg())
-
- "--annotation-allowed-classes-file" ->
- ret.annotationAllowedClassesFile.set(nextArg())
-
- "--default-class-load-hook" ->
- ret.defaultClassLoadHook.set(nextArg())
-
- "--default-method-call-hook" ->
- ret.defaultMethodCallHook.set(nextArg())
-
- "--gen-keep-all-file" ->
- ret.inputJarAsKeepAllFile.set(nextArg())
-
- "--delete-finals" -> ret.deleteFinals.set(true)
-
- // Following options are for debugging.
- "--enable-class-checker" -> ret.enableClassChecker.set(true)
- "--no-class-checker" -> ret.enableClassChecker.set(false)
+ override fun parseOption(option: String, args: ArgIterator): Boolean {
+ // Define some shorthands...
+ fun nextArg(): String = args.nextArgRequired(option)
- "--enable-pre-trace" -> ret.enablePreTrace.set(true)
- "--no-pre-trace" -> ret.enablePreTrace.set(false)
+ when (option) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
- "--enable-post-trace" -> ret.enablePostTrace.set(true)
- "--no-post-trace" -> ret.enablePostTrace.set(false)
+ "--in-jar" -> inJar.set(nextArg()).ensureFileExists()
+ // We support both arguments because some AOSP dependencies
+ // still use the old argument
+ "--out-jar", "--out-impl-jar" -> outJar.set(nextArg())
- "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg())
+ "--clean-up-on-error" -> cleanUpOnError.set(true)
+ "--no-clean-up-on-error" -> cleanUpOnError.set(false)
- "--stats-file" -> ret.statsFile.set(nextArg())
- "--supported-api-list-file" -> ret.apiListFile.set(nextArg())
+ "--gen-input-dump-file" -> inputJarDumpFile.set(nextArg())
+ "--gen-keep-all-file" -> inputJarAsKeepAllFile.set(nextArg())
- "--num-shards" -> ret.numShards.set(nextArg()).also {
- if (it < 1) {
- throw ArgumentsException("$arg must be positive integer")
- }
- }
- "--shard-index" -> ret.shard.set(nextArg()).also {
- if (it < 0) {
- throw ArgumentsException("$arg must be positive integer or zero")
- }
- }
+ "--stats-file" -> statsFile.set(nextArg())
+ "--supported-api-list-file" -> apiListFile.set(nextArg())
- else -> throw ArgumentsException("Unknown option: $arg")
- }
- } catch (e: SetOnce.SetMoreThanOnceException) {
- throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+ "--num-shards" -> numShards.set(nextArg()).also {
+ if (it < 1) {
+ throw ArgumentsException("$option must be positive integer")
}
}
-
- if (!ret.inJar.isSet) {
- throw ArgumentsException("Required option missing: --in-jar")
- }
- if (!ret.outJar.isSet) {
- log.w("--out-jar is not set. $executableName will not generate jar files.")
- }
- if (ret.numShards.isSet != ret.shard.isSet) {
- throw ArgumentsException("--num-shards and --shard-index must be used together")
- }
-
- if (ret.numShards.isSet) {
- if (ret.shard.get >= ret.numShards.get) {
- throw ArgumentsException("--shard-index must be smaller than --num-shards")
+ "--shard-index" -> shard.set(nextArg()).also {
+ if (it < 0) {
+ throw ArgumentsException("$option must be positive integer or zero")
}
}
- return ret
- }
- }
-
- override fun toString(): String {
- return """
- HostStubGenOptions{
- inJar='$inJar',
- outJar='$outJar',
- inputJarDumpFile=$inputJarDumpFile,
- inputJarAsKeepAllFile=$inputJarAsKeepAllFile,
- keepAnnotations=$keepAnnotations,
- throwAnnotations=$throwAnnotations,
- removeAnnotations=$removeAnnotations,
- ignoreAnnotations=$ignoreAnnotations,
- keepClassAnnotations=$keepClassAnnotations,
- partiallyAllowedAnnotations=$partiallyAllowedAnnotations,
- substituteAnnotations=$substituteAnnotations,
- nativeSubstituteAnnotations=$redirectionClassAnnotations,
- classLoadHookAnnotations=$classLoadHookAnnotations,
- keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations,
- packageRedirects=$packageRedirects,
- annotationAllowedClassesFile=$annotationAllowedClassesFile,
- defaultClassLoadHook=$defaultClassLoadHook,
- defaultMethodCallHook=$defaultMethodCallHook,
- policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()},
- defaultPolicy=$defaultPolicy,
- deleteFinals=$deleteFinals,
- cleanUpOnError=$cleanUpOnError,
- enableClassChecker=$enableClassChecker,
- enablePreTrace=$enablePreTrace,
- enablePostTrace=$enablePostTrace,
- statsFile=$statsFile,
- apiListFile=$apiListFile,
- numShards=$numShards,
- shard=$shard,
- }
- """.trimIndent()
- }
-}
-
-class ArgIterator(
- private val args: List<String>,
- private var currentIndex: Int = -1
-) {
- val current: String
- get() = args.get(currentIndex)
-
- /**
- * Get the next argument, or [null] if there's no more arguments.
- */
- fun nextArgOptional(): String? {
- if ((currentIndex + 1) >= args.size) {
- return null
+ else -> return super.parseOption(option, args)
}
- return args.get(++currentIndex)
- }
- /**
- * Get the next argument, or throw if
- */
- fun nextArgRequired(argName: String): String {
- nextArgOptional().let {
- if (it == null) {
- throw ArgumentsException("Missing parameter for option $argName")
- }
- if (it.isEmpty()) {
- throw ArgumentsException("Parameter can't be empty for option $argName")
- }
- return it
- }
+ return true
}
- companion object {
- fun withAtFiles(args: Array<String>): ArgIterator {
- return ArgIterator(expandAtFiles(args))
- }
- }
-}
-
-/**
- * Scan the arguments, and if any of them starts with an `@`, then load from the file
- * and use its content as arguments.
- *
- * In order to pass an argument that starts with an '@', use '@@' instead.
- *
- * In this file, each line is treated as a single argument.
- *
- * The file can contain '#' as comments.
- */
-private fun expandAtFiles(args: Array<String>): List<String> {
- val ret = mutableListOf<String>()
-
- args.forEach { arg ->
- if (arg.startsWith("@@")) {
- ret += arg.substring(1)
- return@forEach
- } else if (!arg.startsWith('@')) {
- ret += arg
- return@forEach
- }
- // Read from the file, and add each line to the result.
- val filename = arg.substring(1).ensureFileExists()
-
- log.v("Expanding options file $filename")
-
- BufferedReader(FileReader(filename)).use { reader ->
- while (true) {
- var line = reader.readLine()
- if (line == null) {
- break // EOF
- }
-
- line = normalizeTextLine(line)
- if (line.isNotEmpty()) {
- ret += line
- }
- }
- }
+ override fun dumpFields(): String {
+ return """
+ inJar=$inJar,
+ outJar=$outJar,
+ inputJarDumpFile=$inputJarDumpFile,
+ inputJarAsKeepAllFile=$inputJarAsKeepAllFile,
+ cleanUpOnError=$cleanUpOnError,
+ statsFile=$statsFile,
+ apiListFile=$apiListFile,
+ numShards=$numShards,
+ shard=$shard,
+ """.trimIndent() + '\n' + super.dumpFields()
}
- return ret
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
deleted file mode 100644
index a3f934cacc2c..000000000000
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.hoststubgen.filters
-
-import com.android.hoststubgen.asm.ClassNodes
-
-/**
- * Filter used by TextFileFilterPolicyParser for "method call relacement".
- */
-class TextFilePolicyMethodReplaceFilter(
- val spec: List<MethodCallReplaceSpec>,
- val classes: ClassNodes,
- val fallback: OutputFilter,
-) : DelegatingFilter(fallback) {
-
- data class MethodCallReplaceSpec(
- val fromClass: String,
- val fromMethod: String,
- val fromDescriptor: String,
- val toClass: String,
- val toMethod: String,
- )
-
- override fun hasAnyMethodCallReplace(): Boolean {
- return true
- }
-
- override fun getMethodCallReplaceTo(
- callerClassName: String,
- callerMethodName: String,
- className: String,
- methodName: String,
- descriptor: String,
- ): MethodReplaceTarget? {
- // Maybe use 'Tri' if we end up having too many replacements.
- spec.forEach {
- if (className == it.fromClass &&
- methodName == it.fromMethod
- ) {
- if (it.fromDescriptor == "*" || descriptor == it.fromDescriptor) {
- return MethodReplaceTarget(it.toClass, it.toMethod)
- }
- }
- }
- return null
- }
-}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
deleted file mode 100644
index bc90d1248322..000000000000
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.hoststubgen.filters
-
-import java.util.regex.Pattern
-
-/**
- * A filter that provides a simple "jarjar" functionality via [mapType]
- */
-class TextFilePolicyRemapperFilter(
- val typeRenameSpecs: List<TypeRenameSpec>,
- fallback: OutputFilter,
-) : DelegatingFilter(fallback) {
- /**
- * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix]
- * to it.
- */
- data class TypeRenameSpec(
- val typeInternalNamePattern: Pattern,
- val typeInternalNamePrefix: String,
- )
-
- override fun remapType(className: String): String? {
- typeRenameSpecs.forEach {
- if (it.typeInternalNamePattern.matcher(className).matches()) {
- return it.typeInternalNamePrefix + className
- }
- }
- return null
- }
-}
diff --git a/ravenwood/tools/ravenhelper/Android.bp b/ravenwood/tools/ravenhelper/Android.bp
index 3da6dd824c37..b27914750618 100644
--- a/ravenwood/tools/ravenhelper/Android.bp
+++ b/ravenwood/tools/ravenhelper/Android.bp
@@ -14,13 +14,7 @@ java_binary_host {
static_libs: [
"guava",
"hoststubgen-lib",
- "junit",
"metalava-gradle-plugin-deps", // Get lint/PSI related classes from here.
- "ow2-asm",
- "ow2-asm-analysis",
- "ow2-asm-commons",
- "ow2-asm-tree",
- "ow2-asm-util",
],
visibility: ["//visibility:public"],
}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt
index 08bd95fd532b..58bd9e987fd1 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt
@@ -15,11 +15,11 @@
*/
package com.android.platform.test.ravenwood.ravenhelper.policytoannot
-import com.android.hoststubgen.ArgIterator
import com.android.hoststubgen.ArgumentsException
-import com.android.hoststubgen.SetOnce
-import com.android.hoststubgen.ensureFileExists
-import com.android.hoststubgen.log
+import com.android.hoststubgen.utils.ArgIterator
+import com.android.hoststubgen.utils.BaseOptions
+import com.android.hoststubgen.utils.SetOnce
+import com.android.hoststubgen.utils.ensureFileExists
/**
* Options for the "ravenhelper pta" subcommand.
@@ -39,68 +39,48 @@ class PtaOptions(
/** Dump the operations (for debugging) */
var dumpOperations: SetOnce<Boolean> = SetOnce(false),
-) {
- companion object {
- fun parseArgs(args: List<String>): PtaOptions {
- val ret = PtaOptions()
- val ai = ArgIterator.withAtFiles(args.toTypedArray())
+) : BaseOptions() {
- while (true) {
- val arg = ai.nextArgOptional() ?: break
+ override fun parseOption(option: String, args: ArgIterator): Boolean {
+ fun nextArg(): String = args.nextArgRequired(option)
- fun nextArg(): String = ai.nextArgRequired(arg)
+ when (option) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
- if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
- continue
- }
- try {
- when (arg) {
- // TODO: Write help
- "-h", "--help" -> TODO("Help is not implemented yet")
+ "-p", "--policy-override-file" ->
+ policyOverrideFiles.add(nextArg().ensureFileExists())
- "-p", "--policy-override-file" ->
- ret.policyOverrideFiles.add(nextArg().ensureFileExists())
+ "-a", "--annotation-allowed-classes-file" ->
+ annotationAllowedClassesFile.set(nextArg().ensureFileExists())
- "-a", "--annotation-allowed-classes-file" ->
- ret.annotationAllowedClassesFile.set(nextArg().ensureFileExists())
+ "-s", "--src" -> sourceFilesOrDirectories.add(nextArg().ensureFileExists())
+ "--dump" -> dumpOperations.set(true)
+ "-o", "--output-script" -> outputScriptFile.set(nextArg())
- "-s", "--src" ->
- ret.sourceFilesOrDirectories.add(nextArg().ensureFileExists())
-
- "--dump" ->
- ret.dumpOperations.set(true)
-
- "-o", "--output-script" ->
- ret.outputScriptFile.set(nextArg())
-
- else -> throw ArgumentsException("Unknown option: $arg")
- }
- } catch (e: SetOnce.SetMoreThanOnceException) {
- throw ArgumentsException("Duplicate or conflicting argument found: $arg")
- }
- }
+ else -> return false
+ }
- if (ret.policyOverrideFiles.size == 0) {
- throw ArgumentsException("Must specify at least one policy file")
- }
+ return true
+ }
- if (ret.sourceFilesOrDirectories.size == 0) {
- throw ArgumentsException("Must specify at least one source path")
- }
+ override fun checkArgs() {
+ if (policyOverrideFiles.size == 0) {
+ throw ArgumentsException("Must specify at least one policy file")
+ }
- return ret
+ if (sourceFilesOrDirectories.size == 0) {
+ throw ArgumentsException("Must specify at least one source path")
}
}
- override fun toString(): String {
+ override fun dumpFields(): String {
return """
- PtaOptions{
- policyOverrideFiles=$policyOverrideFiles
- annotationAllowedClassesFile=$annotationAllowedClassesFile
- sourceFilesOrDirectories=$sourceFilesOrDirectories
- outputScriptFile=$outputScriptFile
- dumpOperations=$dumpOperations
- }
- """.trimIndent()
+ policyOverrideFiles=$policyOverrideFiles
+ annotationAllowedClassesFile=$annotationAllowedClassesFile
+ sourceFilesOrDirectories=$sourceFilesOrDirectories
+ outputScriptFile=$outputScriptFile
+ dumpOperations=$dumpOperations
+ """.trimIndent()
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
index a7f481a02533..5ce9a23e6e05 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
@@ -19,10 +19,10 @@ import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.MethodCallReplaceSpec
import com.android.hoststubgen.filters.PolicyFileProcessor
import com.android.hoststubgen.filters.SpecialClass
import com.android.hoststubgen.filters.TextFileFilterPolicyParser
-import com.android.hoststubgen.filters.TextFilePolicyMethodReplaceFilter
import com.android.hoststubgen.log
import com.android.hoststubgen.utils.ClassPredicate
import com.android.platform.test.ravenwood.ravenhelper.SubcommandHandler
@@ -39,7 +39,7 @@ import java.util.regex.Pattern
*/
class PtaProcessor : SubcommandHandler {
override fun handle(args: List<String>) {
- val options = PtaOptions.parseArgs(args)
+ val options = PtaOptions().apply { parseArgs(args) }
log.v("Options: $options")
@@ -448,7 +448,7 @@ private class TextPolicyToAnnotationConverter(
className: String,
methodName: String,
methodDesc: String,
- replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ replaceSpec: MethodCallReplaceSpec,
) {
// This can't be converted to an annotation.
classHasMember = true
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt
index ee200bb39df2..6e0b7b89cf13 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt
@@ -15,11 +15,11 @@
*/
package com.android.platform.test.ravenwood.ravenhelper.sourcemap
-import com.android.hoststubgen.ArgIterator
import com.android.hoststubgen.ArgumentsException
-import com.android.hoststubgen.SetOnce
-import com.android.hoststubgen.ensureFileExists
-import com.android.hoststubgen.log
+import com.android.hoststubgen.utils.ArgIterator
+import com.android.hoststubgen.utils.BaseOptions
+import com.android.hoststubgen.utils.SetOnce
+import com.android.hoststubgen.utils.ensureFileExists
/**
* Options for the "ravenhelper map" subcommand.
@@ -36,60 +36,36 @@ class MapOptions(
/** Text to insert. */
var text: SetOnce<String?> = SetOnce(null),
-) {
- companion object {
- fun parseArgs(args: List<String>): MapOptions {
- val ret = MapOptions()
- val ai = ArgIterator.withAtFiles(args.toTypedArray())
-
- while (true) {
- val arg = ai.nextArgOptional() ?: break
-
- fun nextArg(): String = ai.nextArgRequired(arg)
-
- if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
- continue
- }
- try {
- when (arg) {
- // TODO: Write help
- "-h", "--help" -> TODO("Help is not implemented yet")
-
- "-s", "--src" ->
- ret.sourceFilesOrDirectories.add(nextArg().ensureFileExists())
-
- "-i", "--input" ->
- ret.targetMethodFiles.add(nextArg().ensureFileExists())
-
- "-o", "--output-script" ->
- ret.outputScriptFile.set(nextArg())
-
- "-t", "--text" ->
- ret.text.set(nextArg())
-
- else -> throw ArgumentsException("Unknown option: $arg")
- }
- } catch (e: SetOnce.SetMoreThanOnceException) {
- throw ArgumentsException("Duplicate or conflicting argument found: $arg")
- }
- }
+) : BaseOptions() {
+
+ override fun parseOption(option: String, args: ArgIterator): Boolean {
+ fun nextArg(): String = args.nextArgRequired(option)
+
+ when (option) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
+ "-s", "--src" -> sourceFilesOrDirectories.add(nextArg().ensureFileExists())
+ "-i", "--input" -> targetMethodFiles.add(nextArg().ensureFileExists())
+ "-o", "--output-script" -> outputScriptFile.set(nextArg())
+ "-t", "--text" -> text.set(nextArg())
+ else -> return false
+ }
- if (ret.sourceFilesOrDirectories.size == 0) {
- throw ArgumentsException("Must specify at least one source path")
- }
+ return true
+ }
- return ret
+ override fun checkArgs() {
+ if (sourceFilesOrDirectories.size == 0) {
+ throw ArgumentsException("Must specify at least one source path")
}
}
- override fun toString(): String {
+ override fun dumpFields(): String {
return """
- PtaOptions{
- sourceFilesOrDirectories=$sourceFilesOrDirectories
- targetMethods=$targetMethodFiles
- outputScriptFile=$outputScriptFile
- text=$text
- }
- """.trimIndent()
+ sourceFilesOrDirectories=$sourceFilesOrDirectories
+ targetMethods=$targetMethodFiles
+ outputScriptFile=$outputScriptFile
+ text=$text
+ """.trimIndent()
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt
index 8085253895f9..f1c139891b2d 100644
--- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt
@@ -35,7 +35,7 @@ import java.io.FileReader
*/
class MarkMethodHandler : SubcommandHandler {
override fun handle(args: List<String>) {
- val options = MapOptions.parseArgs(args)
+ val options = MapOptions().apply { parseArgs(args) }
log.i("Options: $options")
diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp
index a52a04b44f2d..93cda4e3c4c9 100644
--- a/ravenwood/tools/ravenizer/Android.bp
+++ b/ravenwood/tools/ravenizer/Android.bp
@@ -13,13 +13,12 @@ java_binary_host {
srcs: ["src/**/*.kt"],
static_libs: [
"hoststubgen-lib",
- "ow2-asm",
- "ow2-asm-analysis",
- "ow2-asm-commons",
- "ow2-asm-tree",
- "ow2-asm-util",
- "junit",
"ravenwood-junit-for-ravenizer",
],
+ java_resources: [
+ ":ravenizer-standard-options",
+ ":ravenwood-standard-annotations",
+ ":ravenwood-common-policies",
+ ],
visibility: ["//visibility:public"],
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index e67c730df069..04e3bda2ba27 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -16,23 +16,22 @@
package com.android.platform.test.ravenwood.ravenizer
import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.HostStubGenClassProcessor
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.zipEntryNameToClassName
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.ClassVisitor
-import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.util.CheckClassAdapter
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.FileOutputStream
-import java.io.InputStream
-import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.util.CheckClassAdapter
/**
* Various stats on Ravenizer.
@@ -41,7 +40,7 @@ data class RavenizerStats(
/** Total end-to-end time. */
var totalTime: Double = .0,
- /** Time took to build [ClasNodes] */
+ /** Time took to build [ClassNodes] */
var loadStructureTime: Double = .0,
/** Time took to validate the classes */
@@ -50,14 +49,17 @@ data class RavenizerStats(
/** Total real time spent for converting the jar file */
var totalProcessTime: Double = .0,
- /** Total real time spent for converting class files (except for I/O time). */
- var totalConversionTime: Double = .0,
+ /** Total real time spent for ravenizing class files (excluding I/O time). */
+ var totalRavenizeTime: Double = .0,
+
+ /** Total real time spent for processing class files HSG style (excluding I/O time). */
+ var totalHostStubGenTime: Double = .0,
/** Total real time spent for copying class files without modification. */
var totalCopyTime: Double = .0,
/** # of entries in the input jar file */
- var totalEntiries: Int = 0,
+ var totalEntries: Int = 0,
/** # of *.class files in the input jar file */
var totalClasses: Int = 0,
@@ -67,14 +69,15 @@ data class RavenizerStats(
) {
override fun toString(): String {
return """
- RavenizerStats{
+ RavenizerStats {
totalTime=$totalTime,
loadStructureTime=$loadStructureTime,
validationTime=$validationTime,
totalProcessTime=$totalProcessTime,
- totalConversionTime=$totalConversionTime,
+ totalRavenizeTime=$totalRavenizeTime,
+ totalHostStubGenTime=$totalHostStubGenTime,
totalCopyTime=$totalCopyTime,
- totalEntiries=$totalEntiries,
+ totalEntries=$totalEntries,
totalClasses=$totalClasses,
processedClasses=$processedClasses,
}
@@ -90,12 +93,18 @@ class Ravenizer {
val stats = RavenizerStats()
stats.totalTime = log.nTime {
+ val allClasses = ClassNodes.loadClassStructures(options.inJar.get) {
+ stats.loadStructureTime = it
+ }
+ val processor = HostStubGenClassProcessor(options, allClasses)
+
process(
options.inJar.get,
options.outJar.get,
options.enableValidation.get,
options.fatalValidation.get,
options.stripMockito.get,
+ processor,
stats,
)
}
@@ -108,15 +117,13 @@ class Ravenizer {
enableValidation: Boolean,
fatalValidation: Boolean,
stripMockito: Boolean,
+ processor: HostStubGenClassProcessor,
stats: RavenizerStats,
) {
- var allClasses = ClassNodes.loadClassStructures(inJar) {
- time -> stats.loadStructureTime = time
- }
if (enableValidation) {
stats.validationTime = log.iTime("Validating classes") {
- if (!validateClasses(allClasses)) {
- var message = "Invalid test class(es) detected." +
+ if (!validateClasses(processor.allClasses)) {
+ val message = "Invalid test class(es) detected." +
" See error log for details."
if (fatalValidation) {
throw RavenizerInvalidTestException(message)
@@ -126,7 +133,7 @@ class Ravenizer {
}
}
}
- if (includeUnsupportedMockito(allClasses)) {
+ if (includeUnsupportedMockito(processor.allClasses)) {
log.w("Unsupported Mockito detected in $inJar!")
}
@@ -134,7 +141,7 @@ class Ravenizer {
ZipFile(inJar).use { inZip ->
val inEntries = inZip.entries()
- stats.totalEntiries = inZip.size()
+ stats.totalEntries = inZip.size()
ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
while (inEntries.hasMoreElements()) {
@@ -159,9 +166,9 @@ class Ravenizer {
stats.totalClasses += 1
}
- if (className != null && shouldProcessClass(allClasses, className)) {
- stats.processedClasses += 1
- processSingleClass(inZip, entry, outZip, allClasses, stats)
+ if (className != null &&
+ shouldProcessClass(processor.allClasses, className)) {
+ processSingleClass(inZip, entry, outZip, processor, stats)
} else {
// Too slow, let's use merge_zips to bring back the original classes.
copyZipEntry(inZip, entry, outZip, stats)
@@ -201,14 +208,22 @@ class Ravenizer {
inZip: ZipFile,
entry: ZipEntry,
outZip: ZipOutputStream,
- allClasses: ClassNodes,
+ processor: HostStubGenClassProcessor,
stats: RavenizerStats,
) {
+ stats.processedClasses += 1
val newEntry = ZipEntry(entry.name)
outZip.putNextEntry(newEntry)
BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- processSingleClass(entry, bis, outZip, allClasses, stats)
+ var classBytes = bis.readBytes()
+ stats.totalRavenizeTime += log.vTime("Ravenize ${entry.name}") {
+ classBytes = ravenizeSingleClass(entry, classBytes, processor.allClasses)
+ }
+ stats.totalHostStubGenTime += log.vTime("HostStubGen ${entry.name}") {
+ classBytes = processor.processClassBytecode(classBytes)
+ }
+ outZip.write(classBytes)
}
outZip.closeEntry()
}
@@ -217,41 +232,34 @@ class Ravenizer {
* Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
*/
private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
- return !classInternalName.shouldByBypassed()
+ return !classInternalName.shouldBypass()
&& RunnerRewritingAdapter.shouldProcess(classes, classInternalName)
}
- private fun processSingleClass(
+ private fun ravenizeSingleClass(
entry: ZipEntry,
- input: InputStream,
- output: OutputStream,
+ input: ByteArray,
allClasses: ClassNodes,
- stats: RavenizerStats,
- ) {
- val cr = ClassReader(input)
-
- lateinit var data: ByteArray
- stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+ ): ByteArray {
+ val classInternalName = zipEntryNameToClassName(entry.name)
+ ?: throw RavenizerInternalException("Unexpected zip entry name: ${entry.name}")
- val classInternalName = zipEntryNameToClassName(entry.name)
- ?: throw RavenizerInternalException("Unexpected zip entry name: ${entry.name}")
- val flags = ClassWriter.COMPUTE_MAXS
- val cw = ClassWriter(flags)
- var outVisitor: ClassVisitor = cw
+ val flags = ClassWriter.COMPUTE_MAXS
+ val cw = ClassWriter(flags)
+ var outVisitor: ClassVisitor = cw
- val enableChecker = false
- if (enableChecker) {
- outVisitor = CheckClassAdapter(outVisitor)
- }
+ val enableChecker = false
+ if (enableChecker) {
+ outVisitor = CheckClassAdapter(outVisitor)
+ }
- // This must be kept in sync with shouldProcessClass.
- outVisitor = RunnerRewritingAdapter.maybeApply(
- classInternalName, allClasses, outVisitor)
+ // This must be kept in sync with shouldProcessClass.
+ outVisitor = RunnerRewritingAdapter.maybeApply(
+ classInternalName, allClasses, outVisitor)
- cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
+ val cr = ClassReader(input)
+ cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
- data = cw.toByteArray()
- }
- output.write(data)
+ return cw.toByteArray()
}
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
index aee453020fb4..8a09e6d533b8 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -21,6 +21,26 @@ import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.hoststubgen.runMainWithBoilerplate
+import com.android.hoststubgen.utils.JAR_RESOURCE_PREFIX
+import java.nio.file.Paths
+import kotlin.io.path.exists
+
+/**
+ * If this file exits, we also read options from it. This is "unsafe" because it could break
+ * incremental builds, if it sets any flag that affects the output file.
+ * (however, for now, there's no such options.)
+ *
+ * For example, to enable verbose logging, do `echo '-v' > ~/.raveniezr-unsafe`
+ *
+ * (but even the content of this file changes, soong won't rerun the command, so you need to
+ * remove the output first and then do a build again.)
+ */
+private val RAVENIZER_DOTFILE = System.getenv("HOME") + "/.ravenizer-unsafe"
+
+/**
+ * This is the name of the standard option text file embedded inside ravenizer.jar.
+ */
+private const val RAVENIZER_STANDARD_OPTIONS = "texts/ravenizer-standard-options.txt"
/**
* Entry point.
@@ -30,7 +50,15 @@ fun main(args: Array<String>) {
log.setConsoleLogLevel(LogLevel.Info)
runMainWithBoilerplate {
- val options = RavenizerOptions.parseArgs(args)
+ val newArgs = args.toMutableList()
+ newArgs.add(0, "@$JAR_RESOURCE_PREFIX$RAVENIZER_STANDARD_OPTIONS")
+
+ if (Paths.get(RAVENIZER_DOTFILE).exists()) {
+ log.i("Reading options from $RAVENIZER_DOTFILE")
+ newArgs.add(0, "@$RAVENIZER_DOTFILE")
+ }
+
+ val options = RavenizerOptions().apply { parseArgs(newArgs) }
log.i("$executableName started")
log.v("Options: $options")
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index a0e5599c0a7c..5d278bb046ae 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -15,25 +15,11 @@
*/
package com.android.platform.test.ravenwood.ravenizer
-import com.android.hoststubgen.ArgIterator
import com.android.hoststubgen.ArgumentsException
-import com.android.hoststubgen.SetOnce
-import com.android.hoststubgen.ensureFileExists
-import com.android.hoststubgen.log
-import java.nio.file.Paths
-import kotlin.io.path.exists
-
-/**
- * If this file exits, we also read options from it. This is "unsafe" because it could break
- * incremental builds, if it sets any flag that affects the output file.
- * (however, for now, there's no such options.)
- *
- * For example, to enable verbose logging, do `echo '-v' > ~/.raveniezr-unsafe`
- *
- * (but even the content of this file changes, soong won't rerun the command, so you need to
- * remove the output first and then do a build again.)
- */
-private val RAVENIZER_DOTFILE = System.getenv("HOME") + "/.raveniezr-unsafe"
+import com.android.hoststubgen.HostStubGenClassProcessorOptions
+import com.android.hoststubgen.utils.ArgIterator
+import com.android.hoststubgen.utils.SetOnce
+import com.android.hoststubgen.utils.ensureFileExists
class RavenizerOptions(
/** Input jar file*/
@@ -50,72 +36,49 @@ class RavenizerOptions(
/** Whether to remove mockito and dexmaker classes. */
var stripMockito: SetOnce<Boolean> = SetOnce(false),
-) {
- companion object {
-
- fun parseArgs(origArgs: Array<String>): RavenizerOptions {
- val args = origArgs.toMutableList()
- if (Paths.get(RAVENIZER_DOTFILE).exists()) {
- log.i("Reading options from $RAVENIZER_DOTFILE")
- args.add(0, "@$RAVENIZER_DOTFILE")
- }
-
- val ret = RavenizerOptions()
- val ai = ArgIterator.withAtFiles(args.toTypedArray())
-
- while (true) {
- val arg = ai.nextArgOptional()
- if (arg == null) {
- break
- }
-
- fun nextArg(): String = ai.nextArgRequired(arg)
-
- if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
- continue
- }
- try {
- when (arg) {
- // TODO: Write help
- "-h", "--help" -> TODO("Help is not implemented yet")
-
- "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
- "--out-jar" -> ret.outJar.set(nextArg())
-
- "--enable-validation" -> ret.enableValidation.set(true)
- "--disable-validation" -> ret.enableValidation.set(false)
-
- "--fatal-validation" -> ret.fatalValidation.set(true)
- "--no-fatal-validation" -> ret.fatalValidation.set(false)
-
- "--strip-mockito" -> ret.stripMockito.set(true)
- "--no-strip-mockito" -> ret.stripMockito.set(false)
-
- else -> throw ArgumentsException("Unknown option: $arg")
- }
- } catch (e: SetOnce.SetMoreThanOnceException) {
- throw ArgumentsException("Duplicate or conflicting argument found: $arg")
- }
- }
-
- if (!ret.inJar.isSet) {
- throw ArgumentsException("Required option missing: --in-jar")
- }
- if (!ret.outJar.isSet) {
- throw ArgumentsException("Required option missing: --out-jar")
- }
- return ret
+) : HostStubGenClassProcessorOptions() {
+
+ override fun parseOption(option: String, args: ArgIterator): Boolean {
+ fun nextArg(): String = args.nextArgRequired(option)
+
+ when (option) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
+
+ "--in-jar" -> inJar.set(nextArg()).ensureFileExists()
+ "--out-jar" -> outJar.set(nextArg())
+
+ "--enable-validation" -> enableValidation.set(true)
+ "--disable-validation" -> enableValidation.set(false)
+
+ "--fatal-validation" -> fatalValidation.set(true)
+ "--no-fatal-validation" -> fatalValidation.set(false)
+
+ "--strip-mockito" -> stripMockito.set(true)
+ "--no-strip-mockito" -> stripMockito.set(false)
+
+ else -> return super.parseOption(option, args)
+ }
+
+ return true
+ }
+
+ override fun checkArgs() {
+ if (!inJar.isSet) {
+ throw ArgumentsException("Required option missing: --in-jar")
+ }
+ if (!outJar.isSet) {
+ throw ArgumentsException("Required option missing: --out-jar")
}
}
- override fun toString(): String {
+ override fun dumpFields(): String {
return """
- RavenizerOptions{
- inJar=$inJar,
- outJar=$outJar,
- enableValidation=$enableValidation,
- fatalValidation=$fatalValidation,
- }
- """.trimIndent()
+ inJar=$inJar,
+ outJar=$outJar,
+ enableValidation=$enableValidation,
+ fatalValidation=$fatalValidation,
+ stripMockito=$stripMockito,
+ """.trimIndent() + '\n' + super.dumpFields()
}
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 6092fcc9402d..b394a761c7ae 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,8 +15,8 @@
*/
package com.android.platform.test.ravenwood.ravenizer
-import android.platform.test.annotations.internal.InnerRunner
import android.platform.test.annotations.NoRavenizer
+import android.platform.test.annotations.internal.InnerRunner
import android.platform.test.ravenwood.RavenwoodAwareTestRunner
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.findAnyAnnotation
@@ -85,7 +85,7 @@ fun String.isRavenwoodClass(): Boolean {
/**
* Classes that should never be modified.
*/
-fun String.shouldByBypassed(): Boolean {
+fun String.shouldBypass(): Boolean {
if (this.isRavenwoodClass()) {
return true
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
index 61e254b225c3..d252b4dc8ab6 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -20,8 +20,8 @@ import com.android.hoststubgen.asm.isAbstract
import com.android.hoststubgen.asm.startsWithAny
import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.log
-import org.objectweb.asm.tree.ClassNode
import java.util.regex.Pattern
+import org.objectweb.asm.tree.ClassNode
fun validateClasses(classes: ClassNodes): Boolean {
var allOk = true
@@ -37,7 +37,7 @@ fun validateClasses(classes: ClassNodes): Boolean {
*
*/
fun checkClass(cn: ClassNode, classes: ClassNodes): Boolean {
- if (cn.name.shouldByBypassed()) {
+ if (cn.name.shouldBypass()) {
// Class doesn't need to be checked.
return true
}
@@ -145,4 +145,4 @@ com.android.server.power.stats.BatteryStatsTimerTest
private fun isAllowListedLegacyTest(targetClass: ClassNode): Boolean {
return allowListedLegacyTests.contains(targetClass.name)
-} \ No newline at end of file
+}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index b52b3dabd47d..35db3c6f0a6d 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -260,6 +260,16 @@ flag {
}
flag {
+ name: "pointer_up_motion_event_in_touch_exploration"
+ namespace: "accessibility"
+ description: "Allows POINTER_UP motionEvents to trigger during touch exploration."
+ bug: "374930391"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "proxy_use_apps_on_virtual_device_listener"
namespace: "accessibility"
description: "Fixes race condition described in b/286587811"
@@ -336,3 +346,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "hearing_input_change_when_comm_device"
+ namespace: "accessibility"
+ description: "Listen to the CommunicationDeviceChanged to show hearing device input notification."
+ bug: "394070235"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 42834ce20783..703e37fad5ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -287,7 +287,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public static final int INVALID_SERVICE_ID = -1;
- // Each service has an ID. Also provide one for magnification gesture handling
+ // Each service has an ID. Also provide one for magnification gesture handling.
+ // This ID is also used for mouse event handling.
public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0;
private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1;
@@ -520,15 +521,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Nullable IBinder focusedToken) {
return AccessibilityManagerService.this.handleKeyGestureEvent(event);
}
-
- @Override
- public boolean isKeyGestureSupported(int gestureType) {
- return switch (gestureType) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
- KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true;
- default -> false;
- };
- }
};
@VisibleForTesting
@@ -539,7 +531,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
AccessibilitySecurityPolicy securityPolicy,
SystemActionPerformer systemActionPerformer,
AccessibilityWindowManager a11yWindowManager,
- AccessibilityDisplayListener a11yDisplayListener,
+ AccessibilityDisplayListener.DisplayManagerWrapper displayManagerWrapper,
MagnificationController magnificationController,
@Nullable AccessibilityInputFilter inputFilter,
ProxyManager proxyManager,
@@ -558,7 +550,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mSecurityPolicy = securityPolicy;
mSystemActionPerformer = systemActionPerformer;
mA11yWindowManager = a11yWindowManager;
- mA11yDisplayListener = a11yDisplayListener;
+ mA11yDisplayListener = new AccessibilityDisplayListener(displayManagerWrapper,
+ new MainHandler(Looper.getMainLooper()));
mMagnificationController = magnificationController;
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
@@ -604,7 +597,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
this, LocalServices.getService(PackageManagerInternal.class));
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this, mTraceManager);
- mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
+ mA11yDisplayListener = new AccessibilityDisplayListener(
+ new AccessibilityDisplayListener.DisplayManagerWrapper(mContext), mMainHandler);
mMagnificationController = new MagnificationController(
this,
mLock,
@@ -5465,11 +5459,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* A Utility class to handle display state.
*/
public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
- private final DisplayManager mDisplayManager;
+ private final DisplayManagerWrapper mDisplayManager;
private final ArrayList<Display> mDisplaysList = new ArrayList<>();
private int mSystemUiUid = 0;
- AccessibilityDisplayListener(Context context, Handler handler) {
+ AccessibilityDisplayListener(DisplayManagerWrapper displayManager, Handler handler) {
// Avoid concerns about one thread adding displays while another thread removes
// them by ensuring the looper is the main looper and the DisplayListener
// callbacks are always executed on the one main thread.
@@ -5482,7 +5476,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Slog.e(LOG_TAG, errorMessage);
}
- mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager = displayManager;
mDisplayManager.registerDisplayListener(this, handler);
initializeDisplayList();
@@ -5618,7 +5612,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private boolean isValidDisplay(@Nullable Display display) {
- if (display == null || display.getType() == Display.TYPE_OVERLAY) {
+ if (display == null) {
return false;
}
// Private virtual displays are created by the ap and is not allowed to access by other
@@ -5634,6 +5628,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
return true;
}
+
+ /** Wrapper of DisplayManager for testing. */
+ @VisibleForTesting
+ static class DisplayManagerWrapper {
+ private final DisplayManager mDm;
+
+ DisplayManagerWrapper(Context context) {
+ mDm = context.getSystemService(DisplayManager.class);
+ }
+
+ /**
+ * @see DisplayManager#registerDisplayListener(DisplayManager.DisplayListener, Handler)
+ */
+ public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+ @Nullable Handler handler) {
+ mDm.registerDisplayListener(listener, handler);
+ }
+
+ /** @see DisplayManager#getDisplays() */
+ public Display[] getDisplays() {
+ return mDm.getDisplays();
+ }
+
+ /** @see DisplayManager#getDisplay(int) */
+ public Display getDisplay(int displayId) {
+ return mDm.getDisplay(displayId);
+ }
+ }
}
/** Represents an {@link AccessibilityManager} */
diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
index 10dffb59317e..94cef418b6c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
@@ -65,9 +65,9 @@ public class HearingDevicePhoneCallNotificationController {
private final Executor mCallbackExecutor;
public HearingDevicePhoneCallNotificationController(@NonNull Context context) {
- mTelephonyListener = new CallStateListener(context);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mCallbackExecutor = Executors.newSingleThreadExecutor();
+ mTelephonyListener = new CallStateListener(context, mCallbackExecutor);
}
@VisibleForTesting
@@ -109,14 +109,29 @@ public class HearingDevicePhoneCallNotificationController {
AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_BUILTIN_MIC, "");
private final Context mContext;
+ private final Executor mCommDeviceChangedExecutor;
+ private final AudioManager.OnCommunicationDeviceChangedListener mCommDeviceChangedListener;
private NotificationManager mNotificationManager;
private AudioManager mAudioManager;
private BroadcastReceiver mHearingDeviceActionReceiver;
private BluetoothDevice mHearingDevice;
+ private boolean mIsCommDeviceChangedRegistered = false;
private boolean mIsNotificationShown = false;
- CallStateListener(@NonNull Context context) {
+ CallStateListener(@NonNull Context context, @NonNull Executor executor) {
mContext = context;
+ mCommDeviceChangedExecutor = executor;
+ mCommDeviceChangedListener = device -> {
+ if (device == null) {
+ return;
+ }
+ mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(device));
+ if (mHearingDevice != null) {
+ showNotificationIfNeeded();
+ } else {
+ dismissNotificationIfNeeded();
+ }
+ };
}
@Override
@@ -134,6 +149,11 @@ public class HearingDevicePhoneCallNotificationController {
}
if (state == TelephonyManager.CALL_STATE_IDLE) {
+ if (mIsCommDeviceChangedRegistered) {
+ mIsCommDeviceChangedRegistered = false;
+ mAudioManager.removeOnCommunicationDeviceChangedListener(
+ mCommDeviceChangedListener);
+ }
dismissNotificationIfNeeded();
if (mHearingDevice != null) {
@@ -143,10 +163,26 @@ public class HearingDevicePhoneCallNotificationController {
mHearingDevice = null;
}
if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
- mHearingDevice = getSupportedInputHearingDeviceInfo(
- mAudioManager.getAvailableCommunicationDevices());
- if (mHearingDevice != null) {
- showNotificationIfNeeded();
+ if (com.android.server.accessibility.Flags.hearingInputChangeWhenCommDevice()) {
+ AudioDeviceInfo commDevice = mAudioManager.getCommunicationDevice();
+ if (commDevice == null) {
+ return;
+ }
+ mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(commDevice));
+ if (mHearingDevice != null) {
+ showNotificationIfNeeded();
+ } else {
+ mAudioManager.addOnCommunicationDeviceChangedListener(
+ mCommDeviceChangedExecutor,
+ mCommDeviceChangedListener);
+ mIsCommDeviceChangedRegistered = true;
+ }
+ } else {
+ mHearingDevice = getSupportedInputHearingDeviceInfo(
+ mAudioManager.getAvailableCommunicationDevices());
+ if (mHearingDevice != null) {
+ showNotificationIfNeeded();
+ }
}
}
}
@@ -264,6 +300,10 @@ public class HearingDevicePhoneCallNotificationController {
PendingIntent.FLAG_IMMUTABLE);
}
case ACTION_BLUETOOTH_DEVICE_DETAILS -> {
+ if (mHearingDevice == null) {
+ return null;
+ }
+
Bundle bundle = new Bundle();
bundle.putString(KEY_BLUETOOTH_ADDRESS, mHearingDevice.getAddress());
intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 0f6f86b39458..23166a800245 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -21,10 +21,12 @@ import static android.view.MotionEvent.BUTTON_SECONDARY;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT;
import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
@@ -87,6 +89,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
@VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
@VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView;
@VisibleForTesting AutoclickTypePanel mAutoclickTypePanel;
+ @VisibleForTesting AutoclickScrollPanel mAutoclickScrollPanel;
private WindowManager mWindowManager;
// Default click type is left-click.
@@ -98,6 +101,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
@Override
public void handleAutoclickTypeChange(@AutoclickType int clickType) {
mActiveClickType = clickType;
+
+ // Hide scroll panel when type is not scroll.
+ if (clickType != AUTOCLICK_TYPE_SCROLL && mAutoclickScrollPanel != null) {
+ mAutoclickScrollPanel.hide();
+ }
}
@Override
@@ -117,6 +125,22 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
};
+ @VisibleForTesting
+ final AutoclickScrollPanel.ScrollPanelControllerInterface mScrollPanelController =
+ new AutoclickScrollPanel.ScrollPanelControllerInterface() {
+ @Override
+ public void handleScroll(@AutoclickScrollPanel.ScrollDirection int direction) {
+ // TODO(b/388845721): Perform actual scroll.
+ }
+
+ @Override
+ public void exitScrollMode() {
+ if (mAutoclickScrollPanel != null) {
+ mAutoclickScrollPanel.hide();
+ }
+ }
+ };
+
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
mTrace = trace;
mContext = context;
@@ -136,7 +160,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
initiateAutoclickIndicator(handler);
}
- mClickScheduler = new ClickScheduler(handler, AUTOCLICK_DELAY_DEFAULT);
+ mClickScheduler = new ClickScheduler(
+ handler, AUTOCLICK_DELAY_DEFAULT);
mAutoclickSettingsObserver = new AutoclickSettingsObserver(mUserId, handler);
mAutoclickSettingsObserver.start(
mContext.getContentResolver(),
@@ -161,6 +186,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
mWindowManager = mContext.getSystemService(WindowManager.class);
mAutoclickTypePanel =
new AutoclickTypePanel(mContext, mWindowManager, mUserId, clickPanelController);
+ mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager,
+ mScrollPanelController);
mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
@@ -189,6 +216,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
mClickScheduler.cancel();
}
+ if (mAutoclickScrollPanel != null) {
+ mAutoclickScrollPanel.hide();
+ }
+
super.clearEvents(inputSource);
}
@@ -209,6 +240,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
mWindowManager.removeView(mAutoclickIndicatorView);
mAutoclickTypePanel.hide();
}
+
+ if (mAutoclickScrollPanel != null) {
+ mAutoclickScrollPanel.hide();
+ mAutoclickScrollPanel = null;
+ }
}
private void handleMouseMotion(MotionEvent event, int policyFlags) {
@@ -231,7 +267,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
private boolean isPaused() {
return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused()
- && !mAutoclickTypePanel.isHovered();
+ && !isHovered();
+ }
+
+ private boolean isHovered() {
+ return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isHovered();
}
private void cancelPendingClick() {
@@ -266,6 +306,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
+ private final Uri mAutoclickRevertToLeftClickSettingUri =
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK);
+
private ContentResolver mContentResolver;
private ClickScheduler mClickScheduler;
private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
@@ -330,6 +374,13 @@ public class AutoclickController extends BaseEventStreamTransformation {
/* observer= */ this,
mUserId);
onChange(/* selfChange= */ true, mAutoclickIgnoreMinorCursorMovementSettingUri);
+
+ mContentResolver.registerContentObserver(
+ mAutoclickRevertToLeftClickSettingUri,
+ /* notifyForDescendants= */ false,
+ /* observer= */ this,
+ mUserId);
+ onChange(/* selfChange= */ true, mAutoclickRevertToLeftClickSettingUri);
}
}
@@ -386,6 +437,20 @@ public class AutoclickController extends BaseEventStreamTransformation {
== AccessibilityUtils.State.ON;
mClickScheduler.setIgnoreMinorCursorMovement(ignoreMinorCursorMovement);
}
+
+ if (mAutoclickRevertToLeftClickSettingUri.equals(uri)) {
+ boolean revertToLeftClick =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure
+ .ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
+ AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT
+ ? AccessibilityUtils.State.ON
+ : AccessibilityUtils.State.OFF,
+ mUserId)
+ == AccessibilityUtils.State.ON;
+ mClickScheduler.setRevertToLeftClick(revertToLeftClick);
+ }
}
}
}
@@ -467,6 +532,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
/** Whether the minor cursor movement should be ignored. */
private boolean mIgnoreMinorCursorMovement = AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
+ /** Whether the autoclick type reverts to left click once performing an action. */
+ private boolean mRevertToLeftClick = AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT;
+
/** Whether there is pending click. */
private boolean mActive;
/** If active, time at which pending click is scheduled. */
@@ -478,6 +546,8 @@ public class AutoclickController extends BaseEventStreamTransformation {
private int mEventPolicyFlags;
/** Current meta state. This value will be used as meta state for click event sequence. */
private int mMetaState;
+ /** Last observed panel hovered state when click was scheduled. */
+ private boolean mHoveredState;
/**
* The current anchor's coordinates. Should be ignored if #mLastMotionEvent is null.
@@ -515,6 +585,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
sendClick();
resetInternalState();
+ resetSelectedClickTypeIfNecessary();
}
/**
@@ -593,6 +664,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
return mDelay;
}
+ @VisibleForTesting
+ boolean getRevertToLeftClickForTesting() {
+ return mRevertToLeftClick;
+ }
+
/**
* Updates the time at which click sequence should occur.
*
@@ -631,6 +707,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
mLastMotionEvent = MotionEvent.obtain(event);
mEventPolicyFlags = policyFlags;
+ mHoveredState = isHovered();
if (useAsAnchor) {
final int pointerIndex = mLastMotionEvent.getActionIndex();
@@ -651,6 +728,12 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
}
+ private void resetSelectedClickTypeIfNecessary() {
+ if (mRevertToLeftClick && mActiveClickType != AUTOCLICK_TYPE_LEFT_CLICK) {
+ mAutoclickTypePanel.resetSelectedClickType();
+ }
+ }
+
/**
* @param event Observed motion event.
* @return Whether the event coords are far enough from the anchor for the event not to be
@@ -675,6 +758,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
mIgnoreMinorCursorMovement = ignoreMinorCursorMovement;
}
+ public void setRevertToLeftClick(boolean revertToLeftClick) {
+ mRevertToLeftClick = revertToLeftClick;
+ }
+
private void updateMovementSlop(double slop) {
mMovementSlop = slop;
}
@@ -687,6 +774,14 @@ public class AutoclickController extends BaseEventStreamTransformation {
return;
}
+ // Handle scroll type specially, show scroll panel instead of sending click events.
+ if (mActiveClickType == AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL) {
+ if (mAutoclickScrollPanel != null) {
+ mAutoclickScrollPanel.show();
+ }
+ return;
+ }
+
final int pointerIndex = mLastMotionEvent.getActionIndex();
if (mTempPointerProperties == null) {
@@ -704,14 +799,18 @@ public class AutoclickController extends BaseEventStreamTransformation {
final long now = SystemClock.uptimeMillis();
- // TODO(b/395094903): always triggers left-click when the cursor hovers over the
- // autoclick type panel, to always allow users to change a different click type.
- // Otherwise, if one chooses the right-click, this user won't be able to rely on
- // autoclick to select other click types.
- final int actionButton =
- mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
- ? BUTTON_SECONDARY
- : BUTTON_PRIMARY;
+ int actionButton;
+ if (mHoveredState) {
+ // Always triggers left-click when the cursor hovers over the autoclick type
+ // panel, to always allow users to change a different click type. Otherwise, if
+ // one chooses the right-click, this user won't be able to rely on autoclick to
+ // select other click types.
+ actionButton = BUTTON_PRIMARY;
+ } else {
+ actionButton = mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
+ ? BUTTON_SECONDARY
+ : BUTTON_PRIMARY;
+ }
MotionEvent downEvent =
MotionEvent.obtain(
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java
new file mode 100644
index 000000000000..e79be502a6fc
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.autoclick;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.ImageButton;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AutoclickScrollPanel {
+ public static final int DIRECTION_UP = 0;
+ public static final int DIRECTION_DOWN = 1;
+ public static final int DIRECTION_LEFT = 2;
+ public static final int DIRECTION_RIGHT = 3;
+
+ @IntDef({
+ DIRECTION_UP,
+ DIRECTION_DOWN,
+ DIRECTION_LEFT,
+ DIRECTION_RIGHT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScrollDirection {}
+
+ private final Context mContext;
+ private final View mContentView;
+ private final WindowManager mWindowManager;
+ private ScrollPanelControllerInterface mScrollPanelController;
+
+ // Scroll panel buttons.
+ private final ImageButton mUpButton;
+ private final ImageButton mDownButton;
+ private final ImageButton mLeftButton;
+ private final ImageButton mRightButton;
+ private final ImageButton mExitButton;
+
+ private boolean mInScrollMode = false;
+
+ /**
+ * Interface for handling scroll operations.
+ */
+ public interface ScrollPanelControllerInterface {
+ /**
+ * Called when a scroll direction is hovered.
+ *
+ * @param direction The direction to scroll: one of {@link ScrollDirection} values.
+ */
+ void handleScroll(@ScrollDirection int direction);
+
+ /**
+ * Called when the exit button is hovered.
+ */
+ void exitScrollMode();
+ }
+
+ public AutoclickScrollPanel(Context context, WindowManager windowManager,
+ ScrollPanelControllerInterface controller) {
+ mContext = context;
+ mWindowManager = windowManager;
+ mScrollPanelController = controller;
+ mContentView = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_autoclick_scroll_panel, null);
+
+ // Initialize buttons.
+ mUpButton = mContentView.findViewById(R.id.scroll_up);
+ mLeftButton = mContentView.findViewById(R.id.scroll_left);
+ mRightButton = mContentView.findViewById(R.id.scroll_right);
+ mDownButton = mContentView.findViewById(R.id.scroll_down);
+ mExitButton = mContentView.findViewById(R.id.scroll_exit);
+
+ initializeButtonState();
+ }
+
+ /**
+ * Sets up hover listeners for scroll panel buttons.
+ */
+ private void initializeButtonState() {
+ // Set up hover listeners for direction buttons.
+ setupHoverListenerForDirectionButton(mUpButton, DIRECTION_UP);
+ setupHoverListenerForDirectionButton(mLeftButton, DIRECTION_LEFT);
+ setupHoverListenerForDirectionButton(mRightButton, DIRECTION_RIGHT);
+ setupHoverListenerForDirectionButton(mDownButton, DIRECTION_DOWN);
+
+ // Set up hover listener for exit button.
+ mExitButton.setOnHoverListener((v, event) -> {
+ if (mScrollPanelController != null) {
+ mScrollPanelController.exitScrollMode();
+ }
+ return true;
+ });
+ }
+
+ /**
+ * Shows the autoclick scroll panel.
+ */
+ public void show() {
+ if (mInScrollMode) {
+ return;
+ }
+ mWindowManager.addView(mContentView, getLayoutParams());
+ mInScrollMode = true;
+ }
+
+ /**
+ * Hides the autoclick scroll panel.
+ */
+ public void hide() {
+ if (!mInScrollMode) {
+ return;
+ }
+ mWindowManager.removeView(mContentView);
+ mInScrollMode = false;
+ }
+
+ /**
+ * Sets up a hover listener for a direction button.
+ */
+ private void setupHoverListenerForDirectionButton(ImageButton button,
+ @ScrollDirection int direction) {
+ button.setOnHoverListener((v, event) -> {
+ if (mScrollPanelController != null) {
+ mScrollPanelController.handleScroll(direction);
+ }
+ return true;
+ });
+ }
+
+ /**
+ * Retrieves the layout params for AutoclickScrollPanel, used when it's added to the Window
+ * Manager.
+ */
+ @NonNull
+ private WindowManager.LayoutParams getLayoutParams() {
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars());
+ layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ layoutParams.format = PixelFormat.TRANSLUCENT;
+ layoutParams.setTitle(AutoclickScrollPanel.class.getSimpleName());
+ layoutParams.accessibilityTitle =
+ mContext.getString(R.string.accessibility_autoclick_scroll_panel_title);
+ layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ layoutParams.gravity = Gravity.CENTER;
+ return layoutParams;
+ }
+
+ @VisibleForTesting
+ public boolean isVisible() {
+ return mInScrollMode;
+ }
+
+ @VisibleForTesting
+ public View getContentViewForTesting() {
+ return mContentView;
+ }
+
+ @VisibleForTesting
+ public WindowManager.LayoutParams getLayoutParamsForTesting() {
+ return getLayoutParams();
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 57bbb4a7a0a7..5a484d42eb96 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -86,13 +86,6 @@ public class AutoclickTypePanel {
})
public @interface Corner {}
- private static final @Corner int[] CORNER_ROTATION_ORDER = {
- CORNER_BOTTOM_RIGHT,
- CORNER_BOTTOM_LEFT,
- CORNER_TOP_LEFT,
- CORNER_TOP_RIGHT
- };
-
// An interface exposed to {@link AutoclickController) to handle different actions on the panel,
// including changing autoclick type, pausing/resuming autoclick.
public interface ClickPanelControllerInterface {
@@ -136,10 +129,9 @@ public class AutoclickTypePanel {
// Whether autoclick is paused.
private boolean mPaused = false;
- // Tracks the current corner position of the panel using an index into CORNER_ROTATION_ORDER
- // array. This allows the panel to cycle through screen corners in a defined sequence when
- // repositioned.
- private int mCurrentCornerIndex = 0;
+
+ // The current corner position of the panel, default to bottom right.
+ private @Corner int mCurrentCorner = CORNER_BOTTOM_RIGHT;
private final LinearLayout mLeftClickButton;
private final LinearLayout mRightClickButton;
@@ -257,13 +249,13 @@ public class AutoclickTypePanel {
params.gravity = Gravity.START | Gravity.TOP;
// Set the current corner to be bottom-left to ensure that the subsequent reposition
// action rotates the panel clockwise from bottom-left towards top-left.
- mCurrentCornerIndex = 1;
+ mCurrentCorner = CORNER_BOTTOM_LEFT;
} else {
// Snap to right edge. Set params.gravity to make sure x, y offsets from correct anchor.
params.gravity = Gravity.END | Gravity.TOP;
// Set the current corner to be top-right to ensure that the subsequent reposition
// action rotates the panel clockwise from top-right towards bottom-right.
- mCurrentCornerIndex = 3;
+ mCurrentCorner = CORNER_TOP_RIGHT;
}
// Apply final position: set params.x to be edge margin, params.y to maintain vertical
@@ -274,16 +266,28 @@ public class AutoclickTypePanel {
}
private void initializeButtonState() {
- mLeftClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_LEFT_CLICK));
- mRightClickButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_RIGHT_CLICK));
+ // Use `createButtonListener()` to append extra pause logic to each button's click.
+ mLeftClickButton.setOnClickListener(
+ wrapWithTogglePauseListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_LEFT_CLICK)));
+ mRightClickButton.setOnClickListener(
+ wrapWithTogglePauseListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_RIGHT_CLICK)));
mDoubleClickButton.setOnClickListener(
- v -> togglePanelExpansion(AUTOCLICK_TYPE_DOUBLE_CLICK));
- mScrollButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_SCROLL));
- mDragButton.setOnClickListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_DRAG));
- mPositionButton.setOnClickListener(v -> moveToNextCorner());
+ wrapWithTogglePauseListener(
+ v -> togglePanelExpansion(AUTOCLICK_TYPE_DOUBLE_CLICK)));
+ mScrollButton.setOnClickListener(
+ wrapWithTogglePauseListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_SCROLL)));
+ mDragButton.setOnClickListener(
+ wrapWithTogglePauseListener(v -> togglePanelExpansion(AUTOCLICK_TYPE_DRAG)));
+ mPositionButton.setOnClickListener(wrapWithTogglePauseListener(v -> moveToNextCorner()));
+
+ // The pause button calls `togglePause()` directly so it does not need extra logic.
mPauseButton.setOnClickListener(v -> togglePause());
- // Initializes panel as collapsed state and only displays the left click button.
+ resetSelectedClickType();
+ }
+
+ /** Reset panel as collapsed state and only displays the left click button. */
+ public void resetSelectedClickType() {
hideAllClickTypeButtons();
mLeftClickButton.setVisibility(View.VISIBLE);
setSelectedClickType(AUTOCLICK_TYPE_LEFT_CLICK);
@@ -415,10 +419,10 @@ public class AutoclickTypePanel {
/** Moves the panel to the next corner in clockwise direction. */
private void moveToNextCorner() {
- @Corner int nextCornerIndex = (mCurrentCornerIndex + 1) % CORNER_ROTATION_ORDER.length;
- mCurrentCornerIndex = nextCornerIndex;
+ @Corner int nextCorner = (mCurrentCorner + 1) % 4;
+ mCurrentCorner = nextCorner;
- setPanelPositionForCorner(mParams, mCurrentCornerIndex);
+ setPanelPositionForCorner(mParams, mCurrentCorner);
mWindowManager.updateViewLayout(mContentView, mParams);
}
@@ -457,7 +461,7 @@ public class AutoclickTypePanel {
String.valueOf(mParams.gravity),
String.valueOf(mParams.x),
String.valueOf(mParams.y),
- String.valueOf(mCurrentCornerIndex)
+ String.valueOf(mCurrentCorner)
});
Settings.Secure.putStringForUser(mContext.getContentResolver(),
ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, positionString, mUserId);
@@ -473,7 +477,7 @@ public class AutoclickTypePanel {
ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mUserId);
if (savedPosition == null) {
setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
- mCurrentCornerIndex = 0;
+ mCurrentCorner = CORNER_BOTTOM_RIGHT;
return;
}
@@ -481,7 +485,7 @@ public class AutoclickTypePanel {
String[] parts = TextUtils.split(savedPosition, POSITION_DELIMITER);
if (!isValidPositionParts(parts)) {
setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
- mCurrentCornerIndex = 0;
+ mCurrentCorner = CORNER_BOTTOM_RIGHT;
return;
}
@@ -489,7 +493,7 @@ public class AutoclickTypePanel {
mParams.gravity = Integer.parseInt(parts[0]);
mParams.x = Integer.parseInt(parts[1]);
mParams.y = Integer.parseInt(parts[2]);
- mCurrentCornerIndex = Integer.parseInt(parts[3]);
+ mCurrentCorner = Integer.parseInt(parts[3]);
}
private boolean isValidPositionParts(String[] parts) {
@@ -525,6 +529,18 @@ public class AutoclickTypePanel {
return true;
}
+ /* Appends a check of the pause state to the button's listener. */
+ private View.OnClickListener wrapWithTogglePauseListener(View.OnClickListener listener) {
+ return v -> {
+ listener.onClick(v);
+
+ // Resumes autoclick if the button is clicked while in a paused state.
+ if (mPaused) {
+ togglePause();
+ }
+ };
+ }
+
@VisibleForTesting
boolean getExpansionStateForTesting() {
return mExpanded;
@@ -538,8 +554,8 @@ public class AutoclickTypePanel {
@VisibleForTesting
@Corner
- int getCurrentCornerIndexForTesting() {
- return mCurrentCornerIndex;
+ int getCurrentCornerForTesting() {
+ return mCurrentCorner;
}
@VisibleForTesting
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index fb329430acb2..b02fe2752a62 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -653,6 +653,14 @@ public class TouchExplorer extends BaseEventStreamTransformation
case ACTION_UP:
handleActionUp(event, rawEvent, policyFlags);
break;
+ case ACTION_POINTER_UP:
+ if (com.android.server.accessibility.Flags
+ .pointerUpMotionEventInTouchExploration()) {
+ if (mState.isServiceDetectingGestures()) {
+ mAms.sendMotionEventToListeningServices(rawEvent);
+ }
+ }
+ break;
default:
break;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index cd46b38272c2..568abd196735 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -26,6 +26,8 @@ import android.view.Display;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.accessibility.AccessibilityManagerService;
/**
@@ -73,7 +75,8 @@ public class TouchState {
private int mState = STATE_CLEAR;
// Helper class to track received pointers.
// Todo: collapse or hide this class so multiple classes don't modify it.
- private final ReceivedPointerTracker mReceivedPointerTracker;
+ @VisibleForTesting
+ public final ReceivedPointerTracker mReceivedPointerTracker;
// The most recently received motion event.
private MotionEvent mLastReceivedEvent;
// The accompanying raw event without any transformations.
@@ -219,8 +222,19 @@ public class TouchState {
startTouchInteracting();
break;
case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
- setState(STATE_CLEAR);
- // We will clear when we actually handle the next ACTION_DOWN.
+ // When interaction ends, check if there are still down pointers.
+ // If there are any down pointers, go directly to TouchExploring instead.
+ if (com.android.server.accessibility.Flags
+ .pointerUpMotionEventInTouchExploration()) {
+ if (mReceivedPointerTracker.mReceivedPointersDown > 0) {
+ startTouchExploring();
+ } else {
+ setState(STATE_CLEAR);
+ // We will clear when we actually handle the next ACTION_DOWN.
+ }
+ } else {
+ setState(STATE_CLEAR);
+ }
break;
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
startTouchExploring();
@@ -419,7 +433,8 @@ public class TouchState {
private final PointerDownInfo[] mReceivedPointers = new PointerDownInfo[MAX_POINTER_COUNT];
// Which pointers are down.
- private int mReceivedPointersDown;
+ @VisibleForTesting
+ public int mReceivedPointersDown;
// The edge flags of the last received down event.
private int mLastReceivedDownEdgeFlags;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 11b8ccb70dfb..004b3ffcb02b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -121,6 +121,8 @@ public class FullScreenMagnificationController implements
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
@NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
+ private boolean mIsPointerMotionFilterInstalled = false;
+
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
* magnification information per display.
@@ -830,9 +832,17 @@ public class FullScreenMagnificationController implements
return;
}
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+ setOffset(mCurrentMagnificationSpec.offsetX - offsetX,
+ mCurrentMagnificationSpec.offsetY - offsetY, id);
+ }
+
+ @GuardedBy("mLock")
+ void setOffset(float offsetX, float offsetY, int id) {
+ if (!mRegistered) {
+ return;
+ }
+
+ if (updateCurrentSpecWithOffsetsLocked(offsetX, offsetY)) {
onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
if (id != INVALID_SERVICE_ID) {
@@ -1065,6 +1075,7 @@ public class FullScreenMagnificationController implements
if (display.register()) {
mDisplays.put(displayId, display);
mScreenStateObserver.registerIfNecessary();
+ configurePointerMotionFilter(true);
}
}
}
@@ -1613,6 +1624,28 @@ public class FullScreenMagnificationController implements
}
/**
+ * Sets the offset of the magnified region.
+ *
+ * @param displayId The logical display id.
+ * @param offsetX the offset of the magnified region in the X coordinate, in current
+ * screen pixels.
+ * @param offsetY the offset of the magnified region in the Y coordinate, in current
+ * screen pixels.
+ * @param id the ID of the service requesting the change
+ */
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
+ public void setOffset(int displayId, float offsetX, float offsetY, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.setOffset(offsetX, offsetY, id);
+ }
+ }
+
+ /**
* Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
* opposite direction as the offsets passed in here.
*
@@ -1885,6 +1918,7 @@ public class FullScreenMagnificationController implements
}
if (!hasRegister) {
mScreenStateObserver.unregister();
+ configurePointerMotionFilter(false);
}
}
@@ -1900,6 +1934,22 @@ public class FullScreenMagnificationController implements
}
}
+ private void configurePointerMotionFilter(boolean enabled) {
+ if (!Flags.enableMagnificationFollowsMouseWithPointerMotionFilter()) {
+ return;
+ }
+ if (enabled == mIsPointerMotionFilterInstalled) {
+ return;
+ }
+ if (!enabled) {
+ mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter(null);
+ } else {
+ mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter(
+ new FullScreenMagnificationPointerMotionEventFilter(this));
+ }
+ mIsPointerMotionFilterInstalled = enabled;
+ }
+
private boolean traceEnabled() {
return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
FLAGS_WINDOW_MANAGER_INTERNAL);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index e0dd8b601a3d..59b4a1613e08 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -182,6 +182,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
private final int mMinimumVelocity;
private final int mMaximumVelocity;
+ @Nullable
private final MouseEventHandler mMouseEventHandler;
public FullScreenMagnificationGestureHandler(
@@ -313,7 +314,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize(
R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop);
mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- mMouseEventHandler = new MouseEventHandler(mFullScreenMagnificationController);
+ mMouseEventHandler =
+ Flags.enableMagnificationFollowsMouseWithPointerMotionFilter()
+ ? null : new MouseEventHandler(mFullScreenMagnificationController);
if (mDetectShortcutTrigger) {
mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -337,9 +340,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@Override
void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (!mFullScreenMagnificationController.isActivated(mDisplayId)) {
+ if (mMouseEventHandler == null
+ || !mFullScreenMagnificationController.isActivated(mDisplayId)) {
return;
}
+
// TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
// over, rather than only interacting with the current display.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java
new file mode 100644
index 000000000000..f1ba83e80d54
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.magnification;
+
+import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
+
+import android.annotation.NonNull;
+
+import com.android.server.input.InputManagerInternal;
+
+/**
+ * Handles pointer motion event for full screen magnification.
+ * Responsible for controlling magnification's cursor following feature.
+ */
+public class FullScreenMagnificationPointerMotionEventFilter implements
+ InputManagerInternal.AccessibilityPointerMotionFilter {
+
+ private final FullScreenMagnificationController mController;
+
+ public FullScreenMagnificationPointerMotionEventFilter(
+ FullScreenMagnificationController controller) {
+ mController = controller;
+ }
+
+ /**
+ * This call happens on the input hot path and it is extremely performance sensitive. It
+ * also must not call back into native code.
+ */
+ @Override
+ @NonNull
+ public float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY,
+ int displayId) {
+ if (!mController.isActivated(displayId)) {
+ // unrelated display.
+ return new float[]{dx, dy};
+ }
+
+ // TODO(361817142): implement centered and edge following types.
+
+ // Continuous cursor following.
+ float scale = mController.getScale(displayId);
+ final float newCursorX = currentX + dx;
+ final float newCursorY = currentY + dy;
+ mController.setOffset(displayId,
+ newCursorX - newCursorX * scale, newCursorY - newCursorY * scale,
+ MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ // In the continuous mode, the cursor speed in physical display is kept.
+ // Thus, we don't consume any motion delta.
+ return new float[]{dx, dy};
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 6ccf5e47ca6c..59566677b1fc 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -39,6 +39,14 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_DELAY_AFTER_ANIMATION_END;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_FILL_DIALOG_DISABLED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_LAST_TRIGGERED_ID_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_SCREEN_HAS_CREDMAN_FIELD;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_AFTER_DELAY;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_SINCE_IME_ANIMATED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_WAIT_FOR_IME_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED;
@@ -157,8 +165,24 @@ public final class PresentationStatsEventLogger {
DETECTION_PREFER_PCC
})
@Retention(RetentionPolicy.SOURCE)
- public @interface DetectionPreference {
- }
+ public @interface DetectionPreference {}
+
+ /**
+ * The fill dialog not shown reason. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.FillDialogNotShownReason}.
+ */
+ @IntDef(prefix = {"FILL_DIALOG_NOT_SHOWN_REASON"}, value = {
+ FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN,
+ FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED,
+ FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD,
+ FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED,
+ FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION,
+ FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED,
+ FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END,
+ FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FillDialogNotShownReason {}
public static final int NOT_SHOWN_REASON_ANY_SHOWN =
AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
@@ -219,6 +243,25 @@ public final class PresentationStatsEventLogger {
public static final int DETECTION_PREFER_PCC =
AUTOFILL_FILL_RESPONSE_REPORTED__DETECTION_PREFERENCE__DETECTION_PREFER_PCC;
+ // Values for AutofillFillResponseReported.fill_dialog_not_shown_reason
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_UNKNOWN;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_FILL_DIALOG_DISABLED;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_SCREEN_HAS_CREDMAN_FIELD;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_LAST_TRIGGERED_ID_CHANGED;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_WAIT_FOR_IME_ANIMATION;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_SINCE_IME_ANIMATED;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_DELAY_AFTER_ANIMATION_END;
+ public static final int FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_AFTER_DELAY;
+
+
private static final int DEFAULT_VALUE_INT = -1;
private final int mSessionId;
@@ -871,6 +914,43 @@ public final class PresentationStatsEventLogger {
}
/**
+ * Set fill_dialog_not_shown_reason
+ * @param reason
+ */
+ public void maybeSetFillDialogNotShownReason(@FillDialogNotShownReason int reason) {
+ mEventInternal.ifPresent(event -> {
+ if ((event.mFillDialogNotShownReason
+ == FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END
+ || event.mFillDialogNotShownReason
+ == FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION) && reason
+ == FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED) {
+ event.mFillDialogNotShownReason = FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY;
+ } else {
+ event.mFillDialogNotShownReason = reason;
+ }
+ });
+ }
+
+ /**
+ * Set fill_dialog_ready_to_show_ms
+ * @param val
+ */
+ public void maybeSetFillDialogReadyToShowMs(long val) {
+ mEventInternal.ifPresent(event -> {
+ event.mFillDialogReadyToShowMs = (int) (val - mSessionStartTimestamp);
+ });
+ }
+
+ /**
+ * Set ime_animation_finish_ms
+ * @param val
+ */
+ public void maybeSetImeAnimationFinishMs(long val) {
+ mEventInternal.ifPresent(event -> {
+ event.mImeAnimationFinishMs = (int) (val - mSessionStartTimestamp);
+ });
+ }
+ /**
* Set the log contains relayout metrics.
* This is being added as a temporary measure to add logging.
* In future, when we map Session's old view states to the new autofill id's as part of fixing
@@ -959,7 +1039,13 @@ public final class PresentationStatsEventLogger {
+ " event.notExpiringResponseDuringAuthCount="
+ event.mFixExpireResponseDuringAuthCount
+ " event.notifyViewEnteredIgnoredDuringAuthCount="
- + event.mNotifyViewEnteredIgnoredDuringAuthCount);
+ + event.mNotifyViewEnteredIgnoredDuringAuthCount
+ + " event.fillDialogNotShownReason="
+ + event.mFillDialogNotShownReason
+ + " event.fillDialogReadyToShowMs="
+ + event.mFillDialogReadyToShowMs
+ + " event.imeAnimationFinishMs="
+ + event.mImeAnimationFinishMs);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -1020,7 +1106,10 @@ public final class PresentationStatsEventLogger {
event.mViewFilledSuccessfullyOnRefillCount,
event.mViewFailedOnRefillCount,
event.mFixExpireResponseDuringAuthCount,
- event.mNotifyViewEnteredIgnoredDuringAuthCount);
+ event.mNotifyViewEnteredIgnoredDuringAuthCount,
+ event.mFillDialogNotShownReason,
+ event.mFillDialogReadyToShowMs,
+ event.mImeAnimationFinishMs);
mEventInternal = Optional.empty();
}
@@ -1087,6 +1176,9 @@ public final class PresentationStatsEventLogger {
// Following are not logged and used only for internal logic
boolean shouldResetShownCount = false;
boolean mHasRelayoutLog = false;
+ @FillDialogNotShownReason int mFillDialogNotShownReason = FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN;
+ int mFillDialogReadyToShowMs = DEFAULT_VALUE_INT;
+ int mImeAnimationFinishMs = DEFAULT_VALUE_INT;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6fdb2b6b83f7..ff3bf2acb080 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -64,6 +64,7 @@ import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREF
import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_PCC;
import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN;
import static com.android.server.autofill.FillResponseEventLogger.HAVE_SAVE_TRIGGER_ID;
+import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_CANCELLED;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_FAILURE;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SESSION_DESTROYED;
import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS;
@@ -80,6 +81,13 @@ import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTIC
import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS;
import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION;
import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN;
+import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
@@ -1416,6 +1424,15 @@ final class Session
// Remove the FillContext as there will never be a response for the service
if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+ // Start a new FillResponse logger for the cancellation case.
+ mFillResponseEventLogger.startLogForNewResponse();
+ mFillResponseEventLogger.maybeSetRequestId(canceledRequest);
+ mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+ mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_CANCELLED);
+ mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+ (int) (SystemClock.elapsedRealtime() - mLatencyBaseTime));
+ mFillResponseEventLogger.logAndEndEvent();
+
final int numContexts = mContexts.size();
// It is most likely the last context, hence search backwards
@@ -5612,6 +5629,10 @@ final class Session
synchronized (mLock) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+ // Set fill_dialog_not_shown_reason to unknown (a.k.a shown). It is needed due
+ // to possible SHOW_FILL_DIALOG_WAIT.
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN);
}
// Just show fill dialog once per fill request, so disabled after shown.
// Note: Cannot disable before requestShowFillDialog() because the method
@@ -5715,6 +5736,15 @@ final class Session
private boolean isFillDialogUiEnabled() {
synchronized (mLock) {
+ if (mSessionFlags.mFillDialogDisabled) {
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED);
+ }
+ if (mSessionFlags.mScreenHasCredmanField) {
+ // Prefer to log "HAS_CREDMAN_FIELD" over "FILL_DIALOG_DISABLED".
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD);
+ }
return !mSessionFlags.mFillDialogDisabled && !mSessionFlags.mScreenHasCredmanField;
}
}
@@ -5779,6 +5809,8 @@ final class Session
|| !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) {
// Last fill dialog triggered ids are changed.
if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED);
return SHOW_FILL_DIALOG_NO;
}
@@ -5805,6 +5837,8 @@ final class Session
// we need to wait for animation to happen. We can't return from here yet.
// This is the situation #2 described above.
Log.d(TAG, "Waiting for ime animation to complete before showing fill dialog");
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION);
mFillDialogRunnable = createFillDialogEvalRunnable(
response, filledId, filterText, flags);
return SHOW_FILL_DIALOG_WAIT;
@@ -5814,9 +5848,15 @@ final class Session
// max of start input time or the ime finish time
long effectiveDuration = currentTimestampMs
- Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs);
+ mPresentationStatsEventLogger.maybeSetFillDialogReadyToShowMs(
+ currentTimestampMs);
+ mPresentationStatsEventLogger.maybeSetImeAnimationFinishMs(
+ Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs));
if (effectiveDuration >= mFillDialogTimeoutMs) {
Log.d(TAG, "Fill dialog not shown since IME has been up for more time than "
+ mFillDialogTimeoutMs + "ms");
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED);
return SHOW_FILL_DIALOG_NO;
} else if (effectiveDuration < mFillDialogMinWaitAfterImeAnimationMs) {
// we need to wait for some time after animation ends
@@ -5824,6 +5864,8 @@ final class Session
response, filledId, filterText, flags);
mHandler.postDelayed(runnable,
mFillDialogMinWaitAfterImeAnimationMs - effectiveDuration);
+ mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason(
+ FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END);
return SHOW_FILL_DIALOG_WAIT;
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
index 9a353fbc45bf..867cd51e1c2b 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -44,6 +44,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
+import com.android.server.backup.BackupRestoreTask.CancellationReason;
import com.android.server.backup.internal.LifecycleOperationStorage;
import java.util.Set;
@@ -298,20 +299,22 @@ public class BackupAgentConnectionManager {
// Offload operation cancellation off the main thread as the cancellation callbacks
// might call out to BackupTransport. Other operations started on the same package
// before the cancellation callback has executed will also be cancelled by the callback.
- Runnable cancellationRunnable = () -> {
- // handleCancel() causes the PerformFullTransportBackupTask to go on to
- // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
- // that the package being backed up doesn't get stuck in restricted mode until the
- // backup time-out elapses.
- for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
- if (DEBUG) {
- Slog.d(TAG,
- mUserIdMsg + "agentDisconnected: will handleCancel(all) for token:"
- + Integer.toHexString(token));
- }
- mUserBackupManagerService.handleCancel(token, true /* cancelAll */);
- }
- };
+ Runnable cancellationRunnable =
+ () -> {
+ // On handleCancel(), the operation will call unbindAgent() which will make
+ // sure the app doesn't get stuck in restricted mode.
+ for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ mUserIdMsg
+ + "agentDisconnected: cancelling for token:"
+ + Integer.toHexString(token));
+ }
+ mUserBackupManagerService.handleCancel(
+ token, CancellationReason.AGENT_DISCONNECTED);
+ }
+ };
getThreadForCancellation(cancellationRunnable).start();
mAgentConnectLock.notifyAll();
diff --git a/services/backup/java/com/android/server/backup/BackupRestoreTask.java b/services/backup/java/com/android/server/backup/BackupRestoreTask.java
index acaab0c54191..7ec5f0d786ed 100644
--- a/services/backup/java/com/android/server/backup/BackupRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/BackupRestoreTask.java
@@ -16,9 +16,12 @@
package com.android.server.backup;
-/**
- * Interface and methods used by the asynchronous-with-timeout backup/restore operations.
- */
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Interface and methods used by the asynchronous-with-timeout backup/restore operations. */
public interface BackupRestoreTask {
// Execute one tick of whatever state machine the task implements
@@ -27,6 +30,24 @@ public interface BackupRestoreTask {
// An operation that wanted a callback has completed
void operationComplete(long result);
- // An operation that wanted a callback has timed out
- void handleCancel(boolean cancelAll);
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CancellationReason.TIMEOUT,
+ CancellationReason.AGENT_DISCONNECTED,
+ CancellationReason.EXTERNAL,
+ CancellationReason.SCHEDULED_JOB_STOPPED,
+ })
+ @interface CancellationReason {
+ // The task timed out.
+ int TIMEOUT = 0;
+ // The agent went away before the task was able to finish (e.g. due to an app crash).
+ int AGENT_DISCONNECTED = 1;
+ // An external caller cancelled the operation (e.g. via BackupManager#cancelBackups).
+ int EXTERNAL = 2;
+ // The job scheduler has stopped an ongoing scheduled backup pass.
+ int SCHEDULED_JOB_STOPPED = 3;
+ }
+
+ /** The task is cancelled for the given {@link CancellationReason}. */
+ void handleCancel(@CancellationReason int cancellationReason);
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 2143aaaa4cd6..b3af444ff9bd 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -102,6 +102,7 @@ import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.backup.BackupRestoreTask.CancellationReason;
import com.android.server.backup.OperationStorage.OpState;
import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.fullbackup.FullBackupEntry;
@@ -168,6 +169,7 @@ import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntConsumer;
/** System service that performs backup/restore operations. */
public class UserBackupManagerService {
@@ -1816,11 +1818,9 @@ public class UserBackupManagerService {
for (Integer token : operationsToCancel) {
mOperationStorage.cancelOperation(
- token, /* cancelAll */
- true,
- operationType -> {
- /* no callback needed here */
- });
+ token,
+ operationType -> {}, // no callback needed here
+ CancellationReason.EXTERNAL);
}
// We don't want the backup jobs to kick in any time soon.
// Reschedules them to run in the distant future.
@@ -1897,19 +1897,17 @@ public class UserBackupManagerService {
}
/** Cancel the operation associated with {@code token}. */
- public void handleCancel(int token, boolean cancelAll) {
+ public void handleCancel(int token, @CancellationReason int cancellationReason) {
// Remove all pending timeout messages of types OpType.BACKUP_WAIT and
// OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
// doesn't require cancellation.
- mOperationStorage.cancelOperation(
- token,
- cancelAll,
- operationType -> {
- if (operationType == OpType.BACKUP_WAIT
- || operationType == OpType.RESTORE_WAIT) {
- mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
+ IntConsumer timeoutCallback =
+ opType -> {
+ if (opType == OpType.BACKUP_WAIT || opType == OpType.RESTORE_WAIT) {
+ mBackupHandler.removeMessages(getMessageIdForOperationType(opType));
}
- });
+ };
+ mOperationStorage.cancelOperation(token, timeoutCallback, cancellationReason);
}
/** Returns {@code true} if a backup is currently running, else returns {@code false}. */
@@ -2219,20 +2217,17 @@ public class UserBackupManagerService {
// offload the mRunningFullBackupTask.handleCancel() call to another thread,
// as we might have to wait for mCancelLock
Runnable endFullBackupRunnable =
- new Runnable() {
- @Override
- public void run() {
- PerformFullTransportBackupTask pftbt = null;
- synchronized (mQueueLock) {
- if (mRunningFullBackupTask != null) {
- pftbt = mRunningFullBackupTask;
- }
- }
- if (pftbt != null) {
- Slog.i(TAG, mLogIdMsg + "Telling running backup to stop");
- pftbt.handleCancel(true);
+ () -> {
+ PerformFullTransportBackupTask pftbt = null;
+ synchronized (mQueueLock) {
+ if (mRunningFullBackupTask != null) {
+ pftbt = mRunningFullBackupTask;
}
}
+ if (pftbt != null) {
+ Slog.i(TAG, mLogIdMsg + "Telling running backup to stop");
+ pftbt.handleCancel(CancellationReason.SCHEDULED_JOB_STOPPED);
+ }
};
new Thread(endFullBackupRunnable, "end-full-backup").start();
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index ebb1194c7c4a..b173f76e5f6f 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -25,6 +25,7 @@ import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_A
import android.annotation.UserIdInt;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
+import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.ApplicationInfo;
@@ -268,6 +269,12 @@ public class FullBackupEngine {
mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mPkg, mAgent);
} catch (IOException e) {
Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
+ // This is likely due to the app process dying.
+ mBackupManagerMonitorEventSender.monitorEvent(
+ BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN,
+ mPkg,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+ /* extras= */ null);
result = BackupTransport.AGENT_ERROR;
} finally {
try {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 0d4364e14e03..7fc9ed3e0213 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -484,7 +484,7 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
}
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
final PackageInfo target = mCurrentTarget;
Slog.w(TAG, "adb backup cancel of " + target);
if (target != null) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index c182c2618fdf..fa67ef5156c1 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -162,7 +162,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// This is true when a backup operation for some package is in progress.
private volatile boolean mIsDoingBackup;
- private volatile boolean mCancelAll;
+ private volatile boolean mCancelled;
private final int mCurrentOpToken;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final BackupEligibilityRules mBackupEligibilityRules;
@@ -199,7 +199,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
if (backupManagerService.isBackupOperationInProgress()) {
Slog.d(TAG, "Skipping full backup. A backup is already in progress.");
- mCancelAll = true;
+ mCancelled = true;
return;
}
@@ -287,25 +287,31 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
synchronized (mCancelLock) {
- // We only support 'cancelAll = true' case for this task. Cancelling of a single package
-
- // due to timeout is handled by SinglePackageBackupRunner and
+ // This callback is only used for cancelling the entire backup operation. Cancelling of
+ // a single package due to timeout is handled by SinglePackageBackupRunner and
// SinglePackageBackupPreflight.
+ if (cancellationReason == CancellationReason.TIMEOUT) {
+ Slog.wtf(TAG, "This task cannot time out");
+ return;
+ }
- if (!cancelAll) {
- Slog.wtf(TAG, "Expected cancelAll to be true.");
+ // We don't cancel the entire operation if a single agent is disconnected unexpectedly.
+ // SinglePackageBackupRunner and SinglePackageBackupPreflight will receive the same
+ // callback and fail gracefully. The operation should then continue to the next package.
+ if (cancellationReason == CancellationReason.AGENT_DISCONNECTED) {
+ return;
}
- if (mCancelAll) {
+ if (mCancelled) {
Slog.d(TAG, "Ignoring duplicate cancel call.");
return;
}
- mCancelAll = true;
+ mCancelled = true;
if (mIsDoingBackup) {
- mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
+ mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancellationReason);
try {
// If we're running a backup we should be connected to a transport
BackupTransportClient transport =
@@ -410,7 +416,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
int backupPackageStatus;
long quota = Long.MAX_VALUE;
synchronized (mCancelLock) {
- if (mCancelAll) {
+ if (mCancelled) {
break;
}
backupPackageStatus = transport.performFullBackup(currentPackage,
@@ -478,7 +484,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
if (nRead > 0) {
out.write(buffer, 0, nRead);
synchronized (mCancelLock) {
- if (!mCancelAll) {
+ if (!mCancelled) {
backupPackageStatus = transport.sendBackupData(nRead);
}
}
@@ -509,7 +515,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
synchronized (mCancelLock) {
mIsDoingBackup = false;
// If mCancelCurrent is true, we have already called cancelFullBackup().
- if (!mCancelAll) {
+ if (!mCancelled) {
if (backupRunnerResult == BackupTransport.TRANSPORT_OK) {
// If we were otherwise in a good state, now interpret the final
// result based on what finishBackup() returns. If we're in a
@@ -607,7 +613,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_BACKUP_CANCELLED);
Slog.w(TAG, "Backup cancelled. package=" + packageName +
- ", cancelAll=" + mCancelAll);
+ ", entire session cancelled=" + mCancelled);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
currentPackage.applicationInfo, /* allowKill= */ true);
@@ -654,7 +660,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
} finally {
- if (mCancelAll) {
+ if (mCancelled) {
backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED;
}
@@ -820,7 +826,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
if (DEBUG) {
Slog.i(TAG, "Preflight cancelled; failing");
}
@@ -974,17 +980,22 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
public void operationComplete(long result) { /* intentionally empty */ }
@Override
- public void handleCancel(boolean cancelAll) {
- Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
+ public void handleCancel(@CancellationReason int cancellationReason) {
+ Slog.w(
+ TAG,
+ "Cancelled backup: " + mTarget.packageName + " reason:" + cancellationReason);
mBackupManagerMonitorEventSender.monitorEvent(
BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL,
mTarget,
BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
- /* extras= */ null);
+ BackupManagerMonitorEventSender.putMonitoringExtra(
+ /* extras= */ null,
+ BackupManagerMonitor.EXTRA_LOG_CANCELLATION_REASON,
+ cancellationReason));
mIsCancelled = true;
// Cancel tasks spun off by this task.
- mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll);
+ mUserBackupManagerService.handleCancel(mEphemeralToken, cancellationReason);
mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
mTarget.applicationInfo, /* allowKill= */ true);
// Free up everyone waiting on this task and its children.
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 87cf8a313651..464dc2dfe1ec 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -34,6 +34,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.BackupRestoreTask.CancellationReason;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
@@ -410,8 +411,8 @@ public class BackupHandler extends Handler {
case MSG_BACKUP_OPERATION_TIMEOUT:
case MSG_RESTORE_OPERATION_TIMEOUT: {
- Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1));
- backupManagerService.handleCancel(msg.arg1, false);
+ Slog.d(TAG, "Timeout for token=" + Integer.toHexString(msg.arg1));
+ backupManagerService.handleCancel(msg.arg1, CancellationReason.TIMEOUT);
break;
}
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
index 0b974e2d0a8a..5aacb2f4f007 100644
--- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -24,6 +24,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.BackupRestoreTask.CancellationReason;
import com.android.server.backup.OperationStorage;
import com.google.android.collect.Sets;
@@ -296,20 +297,18 @@ public class LifecycleOperationStorage implements OperationStorage {
}
/**
- * Cancel the operation associated with {@code token}. Cancellation may be
- * propagated to the operation's callback (a {@link BackupRestoreTask}) if
- * the operation has one, and the cancellation is due to the operation
- * timing out.
+ * Cancel the operation associated with {@code token}. Cancellation may be propagated to the
+ * operation's callback (a {@link BackupRestoreTask}) if the operation has one, and the
+ * cancellation is due to the operation timing out.
*
* @param token the operation token specified when registering the operation
- * @param cancelAll this is passed on when propagating the cancellation
- * @param operationTimedOutCallback a lambda that is invoked with the
- * operation type where the operation is
- * cancelled due to timeout, allowing the
- * caller to do type-specific clean-ups.
+ * @param operationTimedOutCallback a lambda that is invoked with the operation type where the
+ * operation is cancelled due to timeout, allowing the caller to do type-specific clean-ups.
*/
public void cancelOperation(
- int token, boolean cancelAll, IntConsumer operationTimedOutCallback) {
+ int token,
+ IntConsumer operationTimedOutCallback,
+ @CancellationReason int cancellationReason) {
// Notify any synchronous waiters
Operation op = null;
synchronized (mOperationsLock) {
@@ -343,7 +342,7 @@ public class LifecycleOperationStorage implements OperationStorage {
if (DEBUG) {
Slog.v(TAG, "[UserID:" + mUserId + " Invoking cancel on " + op.callback);
}
- op.callback.handleCancel(cancelAll);
+ op.callback.handleCancel(cancellationReason);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 494b9d59a238..8e7a23ccbb25 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -171,7 +171,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* complete backup should be performed.
*
* <p>This task is designed to run on a dedicated thread, with the exception of the {@link
- * #handleCancel(boolean)} method, which can be called from any thread.
+ * BackupRestoreTask#handleCancel(int)} method, which can be called from any thread.
*/
// TODO: Stop poking into BMS state and doing things for it (e.g. synchronizing on public locks)
// TODO: Consider having the caller responsible for some clean-up (like resetting state)
@@ -1208,13 +1208,13 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
*
* <p>Note: This method is inherently racy since there are no guarantees about how much of the
* task will be executed after you made the call.
- *
- * @param cancelAll MUST be {@code true}. Will be removed.
*/
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
// This is called in a thread different from the one that executes method run().
- Preconditions.checkArgument(cancelAll, "Can't partially cancel a key-value backup task");
+ Preconditions.checkArgument(
+ cancellationReason != CancellationReason.TIMEOUT,
+ "Key-value backup task cannot time out");
markCancel();
waitCancel();
}
diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
index cb491c6f384e..f1829b6966a8 100644
--- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
+++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
@@ -79,7 +79,7 @@ public class AdbRestoreFinishedLatch implements BackupRestoreTask {
}
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
Slog.w(TAG, "adb onRestoreFinished() timed out");
mLatch.countDown();
mOperationStorage.removeOperation(mCurrentOpToken);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 707ae03b3964..20f103cdfab4 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -589,6 +589,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
monitoringExtras);
Slog.e(TAG, "Failure getting next package name");
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
nextState = UnifiedRestoreState.FINAL;
return;
} else if (mRestoreDescription == RestoreDescription.NO_MORE_PACKAGES) {
@@ -1307,7 +1308,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// The app has timed out handling a restoring file
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
mOperationStorage.removeOperation(mEphemeralOpToken);
Slog.w(TAG, "Full-data restore target timed out; shutting down");
Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
@@ -1555,7 +1556,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// A call to agent.doRestore() or agent.doRestoreFinished() has timed out
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
mOperationStorage.removeOperation(mEphemeralOpToken);
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
index fad59d23a6dc..855c72acd7ca 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
@@ -389,6 +389,8 @@ public class BackupManagerMonitorDumpsysUtils {
"Agent failure during restore";
case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT ->
"Failed to read data from Transport";
+ case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN ->
+ "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN";
default -> "Unknown log event ID: " + code;
};
return id;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index ce7dcd0fa1d4..03107563ec22 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -299,14 +299,24 @@ public final class AssociationDiskStore {
public void writeLastRemovedAssociation(AssociationInfo association, String reason) {
Slog.i(TAG, "Writing last removed association=" + association.getId() + " to disk...");
+ // Remove indirect identifier i.e. Mac Address
+ AssociationInfo.Builder builder = new AssociationInfo.Builder(association)
+ .setDeviceMacAddress(null);
+ // Set a placeholder display name if it's null because Mac Address and display name can't be
+ // both null.
+ if (association.getDisplayName() == null) {
+ builder.setDisplayName("");
+ }
+ AssociationInfo redactedAssociation = builder.build();
+
final AtomicFile file = createStorageFileForUser(
- association.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
+ redactedAssociation.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
writeToFileSafely(file, out -> {
out.write(String.valueOf(System.currentTimeMillis()).getBytes());
out.write(' ');
out.write(reason.getBytes());
out.write(' ');
- out.write(association.toString().getBytes());
+ out.write(redactedAssociation.toString().getBytes());
});
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index cd9285cdfe91..cbee8391458d 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -58,13 +58,16 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.R;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.utils.PackageUtils;
+import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* Class responsible for handling incoming {@link AssociationRequest}s.
@@ -130,6 +133,12 @@ public class AssociationRequestsProcessor {
private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
+ // Set of profiles for which the association dialog cannot be skipped.
+ private static final Set<String> DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION = new ArraySet<>(
+ Arrays.asList(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+ AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING));
+
private final @NonNull Context mContext;
private final @NonNull PackageManagerInternal mPackageManagerInternal;
private final @NonNull AssociationStore mAssociationStore;
@@ -174,6 +183,7 @@ public class AssociationRequestsProcessor {
// 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER
// to perform discovery NOR to collect user consent).
if (request.isSelfManaged() && !request.isForceConfirmation()
+ && !DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION.contains(request.getDeviceProfile())
&& !willAddRoleHolder(request, packageName, userId)) {
// 2a.1. Create association right away.
createAssociationAndNotifyApplication(request, packageName, userId,
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 6c7c9b3e073d..4c62c0deb2df 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -73,6 +73,8 @@ public class SecureChannel {
private int mVerificationResult = FLAG_FAILURE_UNKNOWN;
private boolean mPskVerified;
+ private final Object mHandshakeLock = new Object();
+
/**
* Create a new secure channel object. This secure channel allows secure messages to be
@@ -342,20 +344,22 @@ public class SecureChannel {
}
private void initiateHandshake() throws IOException, BadHandleException , HandshakeException {
- if (mConnectionContext != null) {
- Slog.d(TAG, "Ukey2 handshake is already completed.");
- return;
- }
+ synchronized (mHandshakeLock) {
+ if (mConnectionContext != null) {
+ Slog.d(TAG, "Ukey2 handshake is already completed.");
+ return;
+ }
- mRole = Role.INITIATOR;
- mHandshakeContext = D2DHandshakeContext.forInitiator();
- mClientInit = mHandshakeContext.getNextHandshakeMessage();
+ mRole = Role.INITIATOR;
+ mHandshakeContext = D2DHandshakeContext.forInitiator();
+ mClientInit = mHandshakeContext.getNextHandshakeMessage();
- // Send Client Init
- if (DEBUG) {
- Slog.d(TAG, "Sending Ukey2 Client Init message");
+ // Send Client Init
+ if (DEBUG) {
+ Slog.d(TAG, "Sending Ukey2 Client Init message");
+ }
+ sendMessage(MessageType.HANDSHAKE_INIT, constructHandshakeInitMessage(mClientInit));
}
- sendMessage(MessageType.HANDSHAKE_INIT, constructHandshakeInitMessage(mClientInit));
}
// In an occasion where both participants try to initiate a handshake, resolve the conflict
@@ -414,8 +418,17 @@ public class SecureChannel {
// Mark "in-progress" upon receiving the first message
mInProgress = true;
+ // Complete a series of handshake exchange and processing
+ synchronized (mHandshakeLock) {
+ completeHandshake(handshakeInitMessage);
+ }
+ }
+
+ private void completeHandshake(byte[] initMessage) throws IOException, HandshakeException,
+ BadHandleException, CryptoException, AlertException {
+
// Handle a potential collision where both devices tried to initiate a connection
- byte[] handshakeMessage = handleHandshakeCollision(handshakeInitMessage);
+ byte[] handshakeMessage = handleHandshakeCollision(initMessage);
// Proceed with the rest of Ukey2 handshake
if (mHandshakeContext == null) { // Server-side logic
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 93b4de856463..caf535ce7a40 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -142,14 +142,6 @@ public class VirtualDeviceManagerService extends SystemService {
@GuardedBy("mVirtualDeviceManagerLock")
private ArrayMap<String, AssociationInfo> mActiveAssociations = new ArrayMap<>();
- private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
- new CompanionDeviceManager.OnAssociationsChangedListener() {
- @Override
- public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) {
- syncVirtualDevicesToCdmAssociations(associations);
- }
- };
-
private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
final Set<Integer> mUsersInLockdown = new ArraySet<>();
@@ -348,33 +340,6 @@ public class VirtualDeviceManagerService extends SystemService {
return true;
}
- private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) {
- Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
- synchronized (mVirtualDeviceManagerLock) {
- if (mVirtualDevices.size() == 0) {
- return;
- }
-
- Set<Integer> activeAssociationIds = new HashSet<>(associations.size());
- for (AssociationInfo association : associations) {
- activeAssociationIds.add(association.getId());
- }
-
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
- int deviceAssociationId = virtualDevice.getAssociationId();
- if (deviceAssociationId != CDM_ASSOCIATION_ID_NONE
- && !activeAssociationIds.contains(deviceAssociationId)) {
- virtualDevicesToRemove.add(virtualDevice);
- }
- }
- }
-
- for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
- virtualDevice.close();
- }
- }
-
void onCdmAssociationsChanged(List<AssociationInfo> associations) {
ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>();
for (int i = 0; i < associations.size(); ++i) {
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 7eb7072520de..a1d39faa7039 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -313,7 +313,8 @@ public class ContextualSearchManagerService extends SystemService {
android.Manifest.permission.CREATE_USERS,
android.Manifest.permission.QUERY_USERS
})
- private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
+ private Intent getContextualSearchIntent(int entrypoint, int userId, String callingPackage,
+ CallbackToken mToken) {
final Intent launchIntent = getResolvedLaunchIntent(userId);
if (launchIntent == null) {
if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
@@ -332,6 +333,9 @@ public class ContextualSearchManagerService extends SystemService {
launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_AUDIO_PLAYING,
mAudioManager.isMusicActive());
}
+ if (Flags.selfInvocation()) {
+ launchIntent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
+ }
boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
final List<IBinder> activityTokens = new ArrayList<>(records.size());
@@ -500,6 +504,7 @@ public class ContextualSearchManagerService extends SystemService {
}
private void startContextualSearchInternal(int entrypoint) {
+ final String callingPackage = mPackageManager.getNameForUid(Binder.getCallingUid());
final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
mAssistDataRequester.cancel();
// Creates a new CallbackToken at mToken and an expiration handler.
@@ -508,7 +513,8 @@ public class ContextualSearchManagerService extends SystemService {
// server has READ_FRAME_BUFFER permission to get the screenshot and because only
// the system server can invoke non-exported activities.
Binder.withCleanCallingIdentity(() -> {
- Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId, mToken);
+ Intent launchIntent = getContextualSearchIntent(entrypoint, callingUserId,
+ callingPackage, mToken);
if (launchIntent != null) {
int result = invokeContextualSearchIntent(launchIntent, callingUserId);
if (DEBUG) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 2aaf6a9c2391..decac40d20f8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -121,6 +121,13 @@ genrule {
out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"],
}
+genrule {
+ name: "statslog-mediarouter-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module mediarouter --javaPackage com.android.server.media --javaClass MediaRouterStatsLog",
+ out: ["com/android/server/media/MediaRouterStatsLog.java"],
+}
+
java_library_static {
name: "services.core.unboosted",
defaults: [
@@ -131,11 +138,13 @@ java_library_static {
"ondeviceintelligence_conditionally",
],
srcs: [
+ ":android.hardware.audio.effect-V1-java-source",
":android.hardware.tv.hdmi.connection-V1-java-source",
":android.hardware.tv.hdmi.earc-V1-java-source",
":android.hardware.tv.mediaquality-V1-java-source",
":statslog-art-java-gen",
":statslog-contexthub-java-gen",
+ ":statslog-mediarouter-java-gen",
":services.core-aidl-sources",
":services.core-sources",
":services.core.protologsrc",
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 296f7cfe93ba..f54027ec0272 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -514,6 +514,7 @@ final class UiModeManagerService extends SystemService {
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
registerVrStateListener();
// register listeners
+ // LINT.IfChange(fi_cb)
context.getContentResolver()
.registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
false, mDarkThemeObserver, 0);
@@ -523,6 +524,7 @@ final class UiModeManagerService extends SystemService {
Secure.getUriFor(ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED),
false, mForceInvertStateObserver, UserHandle.USER_ALL);
}
+ // LINT.ThenChange(/core/java/android/view/ViewRootImpl.java:fi_cb)
context.getContentResolver().registerContentObserver(
Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
mContrastObserver, UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 96b30d4e1285..d60c6c534871 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -165,7 +165,6 @@ public class Watchdog implements Dumpable {
"android.hardware.sensors@1.0::ISensors",
"android.hardware.sensors@2.0::ISensors",
"android.hardware.sensors@2.1::ISensors",
- "android.hardware.vibrator@1.0::IVibrator",
"android.hardware.vr@1.0::IVr",
"android.system.suspend@1.0::ISystemSuspend"
);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 600b124ffbf6..79fdcca9f75d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -190,6 +190,7 @@ public class AccountManagerService
}
final Context mContext;
+ final PackageMonitor mPackageMonitor;
private static final int[] INTERESTING_APP_OPS = new int[] {
AppOpsManager.OP_GET_ACCOUNTS,
@@ -373,7 +374,7 @@ public class AccountManagerService
}, UserHandle.ALL, userFilter, null, null);
// Need to cancel account request notifications if the update/install can access the account
- new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
// Called on a handler, and running as the system
@@ -397,7 +398,8 @@ public class AccountManagerService
return;
}
}
- }.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
+ };
+ mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
// Cancel account request notification if an app op was preventing the account access
for (int i = 0; i < INTERESTING_APP_OPS.length; ++i) {
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index a73a991bc6a6..658ea4c27e4c 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -130,8 +130,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
public class AdbDebuggingManager {
private static final String TAG = AdbDebuggingManager.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final boolean MDNS_DEBUG = false;
private static final String ADBD_SOCKET = "adbd";
private static final String ADB_DIRECTORY = "misc/adb";
@@ -156,8 +154,6 @@ public class AdbDebuggingManager {
@Nullable private final File mUserKeyFile;
@Nullable private final File mTempKeysFile;
- private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
- "persist.adb.tls_server.enable";
private static final String WIFI_PERSISTENT_GUID =
"persist.adb.wifi.guid";
private static final int PAIRING_CODE_LENGTH = 6;
@@ -261,12 +257,10 @@ public class AdbDebuggingManager {
mHandler.sendMessage(msg);
boolean paired = native_pairing_wait();
- if (DEBUG) {
- if (mPublicKey != null) {
- Slog.i(TAG, "Pairing succeeded key=" + mPublicKey);
- } else {
- Slog.i(TAG, "Pairing failed");
- }
+ if (mPublicKey != null) {
+ Slog.i(TAG, "Pairing succeeded key=" + mPublicKey);
+ } else {
+ Slog.i(TAG, "Pairing failed");
}
mNsdManager.unregisterService(this);
@@ -307,7 +301,7 @@ public class AdbDebuggingManager {
@Override
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
- if (MDNS_DEBUG) Slog.i(TAG, "Registered pairing service: " + serviceInfo);
+ Slog.i(TAG, "Registered pairing service: " + serviceInfo);
}
@Override
@@ -319,7 +313,7 @@ public class AdbDebuggingManager {
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
- if (MDNS_DEBUG) Slog.i(TAG, "Unregistered pairing service: " + serviceInfo);
+ Slog.i(TAG, "Unregistered pairing service: " + serviceInfo);
}
@Override
@@ -354,7 +348,7 @@ public class AdbDebuggingManager {
@Override
public void run() {
- if (DEBUG) Slog.d(TAG, "Starting adb port property poller");
+ Slog.d(TAG, "Starting adb port property poller");
// Once adbwifi is enabled, we poll the service.adb.tls.port
// system property until we get the port, or -1 on failure.
// Let's also limit the polling to 10 seconds, just in case
@@ -390,7 +384,7 @@ public class AdbDebuggingManager {
class PortListenerImpl implements AdbConnectionPortListener {
public void onPortReceived(int port) {
- if (DEBUG) Slog.d(TAG, "Received tls port=" + port);
+ Slog.d(TAG, "Received tls port=" + port);
Message msg = mHandler.obtainMessage(port > 0
? AdbDebuggingHandler.MSG_SERVER_CONNECTED
: AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
@@ -419,11 +413,11 @@ public class AdbDebuggingManager {
@Override
public void run() {
- if (DEBUG) Slog.d(TAG, "Entering thread");
+ Slog.d(TAG, "Entering thread");
while (true) {
synchronized (this) {
if (mStopped) {
- if (DEBUG) Slog.d(TAG, "Exiting thread");
+ Slog.d(TAG, "Exiting thread");
return;
}
try {
@@ -448,7 +442,7 @@ public class AdbDebuggingManager {
LocalSocketAddress.Namespace.RESERVED);
mInputStream = null;
- if (DEBUG) Slog.d(TAG, "Creating socket");
+ Slog.d(TAG, "Creating socket");
mSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
mSocket.connect(address);
@@ -549,7 +543,7 @@ public class AdbDebuggingManager {
}
private void closeSocketLocked() {
- if (DEBUG) Slog.d(TAG, "Closing socket");
+ Slog.d(TAG, "Closing socket");
try {
if (mOutputStream != null) {
mOutputStream.close();
@@ -859,7 +853,7 @@ public class AdbDebuggingManager {
private void startAdbDebuggingThread() {
++mAdbEnabledRefCount;
- if (DEBUG) Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount);
+ Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount);
if (mAdbEnabledRefCount > 1) {
return;
}
@@ -875,7 +869,7 @@ public class AdbDebuggingManager {
private void stopAdbDebuggingThread() {
--mAdbEnabledRefCount;
- if (DEBUG) Slog.i(TAG, "stopAdbDebuggingThread ref=" + mAdbEnabledRefCount);
+ Slog.i(TAG, "stopAdbDebuggingThread ref=" + mAdbEnabledRefCount);
if (mAdbEnabledRefCount > 0) {
return;
}
@@ -1093,7 +1087,7 @@ public class AdbDebuggingManager {
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
- SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+ SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
mConnectionPortPoller =
new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
mConnectionPortPoller.start();
@@ -1101,7 +1095,7 @@ public class AdbDebuggingManager {
startAdbDebuggingThread();
mAdbWifiEnabled = true;
- if (DEBUG) Slog.i(TAG, "adb start wireless adb");
+ Slog.i(TAG, "adb start wireless adb");
break;
}
case MSG_ADBDWIFI_DISABLE:
@@ -1143,7 +1137,7 @@ public class AdbDebuggingManager {
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
- SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+ SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
mConnectionPortPoller =
new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
mConnectionPortPoller.start();
@@ -1151,7 +1145,7 @@ public class AdbDebuggingManager {
startAdbDebuggingThread();
mAdbWifiEnabled = true;
- if (DEBUG) Slog.i(TAG, "adb start wireless adb");
+ Slog.i(TAG, "adb start wireless adb");
break;
case MSG_ADBWIFI_DENY:
Settings.Global.putInt(mContentResolver,
@@ -1259,7 +1253,7 @@ public class AdbDebuggingManager {
break;
}
case MSG_ADBD_SOCKET_CONNECTED: {
- if (DEBUG) Slog.d(TAG, "adbd socket connected");
+ Slog.d(TAG, "adbd socket connected");
if (mAdbWifiEnabled) {
// In scenarios where adbd is restarted, the tls port may change.
mConnectionPortPoller =
@@ -1269,7 +1263,7 @@ public class AdbDebuggingManager {
break;
}
case MSG_ADBD_SOCKET_DISCONNECTED: {
- if (DEBUG) Slog.d(TAG, "adbd socket disconnected");
+ Slog.d(TAG, "adbd socket disconnected");
if (mConnectionPortPoller != null) {
mConnectionPortPoller.cancelAndWait();
mConnectionPortPoller = null;
@@ -1477,7 +1471,7 @@ public class AdbDebuggingManager {
}
private void updateUIPairCode(String code) {
- if (DEBUG) Slog.i(TAG, "updateUIPairCode: " + code);
+ Slog.i(TAG, "updateUIPairCode: " + code);
Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code);
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 55d8dba69626..40f7c873eae8 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -143,19 +143,16 @@ public class AdbService extends IAdbManager.Stub {
@Override
public File getAdbKeysFile() {
- return mDebuggingManager == null ? null : mDebuggingManager.getUserKeyFile();
+ return mDebuggingManager.getUserKeyFile();
}
@Override
public File getAdbTempKeysFile() {
- return mDebuggingManager == null ? null : mDebuggingManager.getAdbTempKeysFile();
+ return mDebuggingManager.getAdbTempKeysFile();
}
@Override
public void notifyKeyFilesUpdated() {
- if (mDebuggingManager == null) {
- return;
- }
mDebuggingManager.notifyKeyFilesUpdated();
}
@@ -222,15 +219,14 @@ public class AdbService extends IAdbManager.Stub {
}
}
- private static final String TAG = "AdbService";
- private static final boolean DEBUG = false;
+ private static final String TAG = AdbService.class.getSimpleName();
/**
* The persistent property which stores whether adb is enabled or not.
* May also contain vendor-specific default functions for testing purposes.
*/
private static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
- private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable";
+ static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable";
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -238,7 +234,7 @@ public class AdbService extends IAdbManager.Stub {
private boolean mIsAdbUsbEnabled;
private boolean mIsAdbWifiEnabled;
- private AdbDebuggingManager mDebuggingManager;
+ private final AdbDebuggingManager mDebuggingManager;
private ContentObserver mObserver;
@@ -256,7 +252,7 @@ public class AdbService extends IAdbManager.Stub {
* SystemServer}.
*/
public void systemReady() {
- if (DEBUG) Slog.d(TAG, "systemReady");
+ Slog.d(TAG, "systemReady");
/*
* Use the normal bootmode persistent prop to maintain state of adb across
@@ -287,39 +283,28 @@ public class AdbService extends IAdbManager.Stub {
* Called in response to {@code SystemService.PHASE_BOOT_COMPLETED} from {@code SystemServer}.
*/
public void bootCompleted() {
- if (DEBUG) Slog.d(TAG, "boot completed");
- if (mDebuggingManager != null) {
- mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB);
- mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI);
- }
+ Slog.d(TAG, "boot completed");
+ mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB);
+ mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI);
}
@Override
public void allowDebugging(boolean alwaysAllow, @NonNull String publicKey) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
Preconditions.checkStringNotEmpty(publicKey);
- if (mDebuggingManager != null) {
- mDebuggingManager.allowDebugging(alwaysAllow, publicKey);
- }
+ mDebuggingManager.allowDebugging(alwaysAllow, publicKey);
}
@Override
public void denyDebugging() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager != null) {
- mDebuggingManager.denyDebugging();
- }
+ mDebuggingManager.denyDebugging();
}
@Override
public void clearDebuggingKeys() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager != null) {
- mDebuggingManager.clearDebuggingKeys();
- } else {
- throw new RuntimeException("Cannot clear ADB debugging keys, "
- + "AdbDebuggingManager not enabled");
- }
+ mDebuggingManager.clearDebuggingKeys();
}
/**
@@ -351,25 +336,18 @@ public class AdbService extends IAdbManager.Stub {
public void allowWirelessDebugging(boolean alwaysAllow, @NonNull String bssid) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
Preconditions.checkStringNotEmpty(bssid);
- if (mDebuggingManager != null) {
- mDebuggingManager.allowWirelessDebugging(alwaysAllow, bssid);
- }
+ mDebuggingManager.allowWirelessDebugging(alwaysAllow, bssid);
}
@Override
public void denyWirelessDebugging() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager != null) {
- mDebuggingManager.denyWirelessDebugging();
- }
+ mDebuggingManager.denyWirelessDebugging();
}
@Override
public FingerprintAndPairDevice[] getPairedDevices() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager == null) {
- return null;
- }
Map<String, PairDevice> map = mDebuggingManager.getPairedDevices();
FingerprintAndPairDevice[] ret = new FingerprintAndPairDevice[map.size()];
int i = 0;
@@ -386,17 +364,13 @@ public class AdbService extends IAdbManager.Stub {
public void unpairDevice(@NonNull String fingerprint) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
Preconditions.checkStringNotEmpty(fingerprint);
- if (mDebuggingManager != null) {
- mDebuggingManager.unpairDevice(fingerprint);
- }
+ mDebuggingManager.unpairDevice(fingerprint);
}
@Override
public void enablePairingByPairingCode() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager != null) {
- mDebuggingManager.enablePairingByPairingCode();
- }
+ mDebuggingManager.enablePairingByPairingCode();
}
@Override
@@ -404,42 +378,30 @@ public class AdbService extends IAdbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
Preconditions.checkStringNotEmpty(serviceName);
Preconditions.checkStringNotEmpty(password);
- if (mDebuggingManager != null) {
- mDebuggingManager.enablePairingByQrCode(serviceName, password);
- }
+ mDebuggingManager.enablePairingByQrCode(serviceName, password);
}
@Override
public void disablePairing() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager != null) {
- mDebuggingManager.disablePairing();
- }
+ mDebuggingManager.disablePairing();
}
@Override
public int getAdbWirelessPort() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
- if (mDebuggingManager != null) {
- return mDebuggingManager.getAdbWirelessPort();
- }
- // If ro.adb.secure=0
- return mConnectionPort.get();
+ return mDebuggingManager.getAdbWirelessPort();
}
@Override
public void registerCallback(IAdbCallback callback) throws RemoteException {
- if (DEBUG) {
- Slog.d(TAG, "Registering callback " + callback);
- }
+ Slog.d(TAG, "Registering callback " + callback);
mCallbacks.register(callback);
}
@Override
public void unregisterCallback(IAdbCallback callback) throws RemoteException {
- if (DEBUG) {
- Slog.d(TAG, "Unregistering callback " + callback);
- }
+ Slog.d(TAG, "Unregistering callback " + callback);
mCallbacks.unregister(callback);
}
/**
@@ -500,18 +462,15 @@ public class AdbService extends IAdbManager.Stub {
}
private void setAdbEnabled(boolean enable, byte transportType) {
- if (DEBUG) {
- Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled
- + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType="
- + transportType);
- }
+ Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled
+ + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType=" + transportType);
if (transportType == AdbTransportType.USB && enable != mIsAdbUsbEnabled) {
mIsAdbUsbEnabled = enable;
} else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
mIsAdbWifiEnabled = enable;
if (mIsAdbWifiEnabled) {
- if (!AdbProperties.secure().orElse(false) && mDebuggingManager == null) {
+ if (!AdbProperties.secure().orElse(false)) {
// Start adbd. If this is secure adb, then we defer enabling adb over WiFi.
SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
mConnectionPortPoller =
@@ -545,24 +504,16 @@ public class AdbService extends IAdbManager.Stub {
}
}
- if (mDebuggingManager != null) {
- mDebuggingManager.setAdbEnabled(enable, transportType);
- }
+ mDebuggingManager.setAdbEnabled(enable, transportType);
- if (DEBUG) {
- Slog.d(TAG, "Broadcasting enable = " + enable + ", type = " + transportType);
- }
+ Slog.d(TAG, "Broadcasting enable = " + enable + ", type = " + transportType);
mCallbacks.broadcast((callback) -> {
- if (DEBUG) {
- Slog.d(TAG, "Sending enable = " + enable + ", type = " + transportType
- + " to " + callback);
- }
+ Slog.d(TAG, "Sending enable = " + enable + ", type = " + transportType + " to "
+ + callback);
try {
callback.onDebuggingChanged(enable, transportType);
} catch (RemoteException ex) {
- if (DEBUG) {
- Slog.d(TAG, "Unable to send onDebuggingChanged:", ex);
- }
+ Slog.w(TAG, "Unable to send onDebuggingChanged:", ex);
}
});
}
@@ -600,11 +551,8 @@ public class AdbService extends IAdbManager.Stub {
dump = new DualDumpOutputStream(new IndentingPrintWriter(pw, " "));
}
- if (mDebuggingManager != null) {
- mDebuggingManager.dump(dump, "debugging_manager",
- AdbServiceDumpProto.DEBUGGING_MANAGER);
- }
-
+ mDebuggingManager.dump(dump, "debugging_manager",
+ AdbServiceDumpProto.DEBUGGING_MANAGER);
dump.flush();
} else {
pw.println("Dump current ADB state");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b0b34d0ab9c4..76ba0054583b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16190,14 +16190,16 @@ public class ActivityManagerService extends IActivityManager.Stub
return mUserController.switchUser(targetUserId);
}
+ @Nullable
@Override
- public String getSwitchingFromUserMessage() {
- return mUserController.getSwitchingFromSystemUserMessage();
+ public String getSwitchingFromUserMessage(@UserIdInt int userId) {
+ return mUserController.getSwitchingFromUserMessage(userId);
}
+ @Nullable
@Override
- public String getSwitchingToUserMessage() {
- return mUserController.getSwitchingToSystemUserMessage();
+ public String getSwitchingToUserMessage(@UserIdInt int userId) {
+ return mUserController.getSwitchingToUserMessage(userId);
}
@Override
@@ -16938,13 +16940,13 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) {
- mUserController.setSwitchingFromSystemUserMessage(switchingFromSystemUserMessage);
+ public void setSwitchingFromUserMessage(@UserIdInt int userId, @Nullable String message) {
+ mUserController.setSwitchingFromUserMessage(userId, message);
}
@Override
- public void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) {
- mUserController.setSwitchingToSystemUserMessage(switchingToSystemUserMessage);
+ public void setSwitchingToUserMessage(@UserIdInt int userId, @Nullable String message) {
+ mUserController.setSwitchingToUserMessage(userId, message);
}
@Override
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 81d34f67dee9..fe4fa1068bb3 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -41,6 +41,7 @@ import dalvik.annotation.optimization.NeverCompile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
/**
* Tunable parameters for broadcast dispatch policy
@@ -286,6 +287,17 @@ public class BroadcastConstants {
"max_frozen_outgoing_broadcasts";
private static final int DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS = 32;
+ /**
+ * For {@link BroadcastQueueImpl}: Indicates how long after a process start was initiated,
+ * it should be considered abandoned and discarded.
+ */
+ public long PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS =
+ DEFAULT_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER;
+ private static final String KEY_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS =
+ "pending_cold_start_abandon_timeout_millis";
+ private static final long DEFAULT_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS =
+ TimeUnit.MINUTES.toMillis(5);
+
// Settings override tracking for this instance
private String mSettingsKey;
private SettingsObserver mSettingsObserver;
@@ -434,6 +446,10 @@ public class BroadcastConstants {
MAX_FROZEN_OUTGOING_BROADCASTS = getDeviceConfigInt(
KEY_MAX_FROZEN_OUTGOING_BROADCASTS,
DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS);
+ PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = getDeviceConfigLong(
+ KEY_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS,
+ DEFAULT_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS)
+ * Build.HW_TIMEOUT_MULTIPLIER;
}
// TODO: migrate BroadcastRecord to accept a BroadcastConstants
@@ -491,6 +507,8 @@ public class BroadcastConstants {
PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println();
pw.print(KEY_MAX_FROZEN_OUTGOING_BROADCASTS,
MAX_FROZEN_OUTGOING_BROADCASTS).println();
+ pw.print(KEY_PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS,
+ PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS).println();
pw.decreaseIndent();
pw.println();
}
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index bec5db79ff9d..afd639d6c8c5 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -1961,7 +1961,9 @@ class BroadcastController {
private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }private List<ResolveInfo> collectReceiverComponents(
+ }
+
+ private List<ResolveInfo> collectReceiverComponents(
Intent intent, String resolvedType, int callingUid, int callingPid,
int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index db0562f5750a..c0fe73877c01 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -245,6 +245,24 @@ class BroadcastProcessQueue {
*/
private final ArrayList<BroadcastRecord> mOutgoingBroadcasts = new ArrayList<>();
+ /**
+ * The timestamp, in {@link SystemClock#uptimeMillis()}, at which a cold start was initiated
+ * for the process associated with this queue.
+ *
+ * Note: We could use the already existing {@link ProcessRecord#getStartUptime()} instead
+ * of this, but the need for this timestamp is to identify an issue (b/393898613) where the
+ * suspicion is that process is not attached or getting changed. So, we don't want to rely on
+ * ProcessRecord directly for this purpose.
+ */
+ private long mProcessStartInitiatedTimestampMillis;
+
+ /**
+ * Indicates whether the number of current receivers has been incremented using
+ * {@link ProcessReceiverRecord#incrementCurReceivers()}. This allows to skip decrementing
+ * the receivers when it is not required.
+ */
+ private boolean mCurReceiversIncremented;
+
public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
@NonNull String processName, int uid) {
this.constants = Objects.requireNonNull(constants);
@@ -652,6 +670,52 @@ class BroadcastProcessQueue {
return mActiveFirstLaunch;
}
+ public void incrementCurAppReceivers() {
+ app.mReceivers.incrementCurReceivers();
+ mCurReceiversIncremented = true;
+ }
+
+ public void decrementCurAppReceivers() {
+ if (mCurReceiversIncremented) {
+ app.mReceivers.decrementCurReceivers();
+ mCurReceiversIncremented = false;
+ }
+ }
+
+ public void setProcessStartInitiatedTimestampMillis(@UptimeMillisLong long timestampMillis) {
+ mProcessStartInitiatedTimestampMillis = timestampMillis;
+ }
+
+ @UptimeMillisLong
+ public long getProcessStartInitiatedTimestampMillis() {
+ return mProcessStartInitiatedTimestampMillis;
+ }
+
+ public boolean hasProcessStartInitiationTimedout() {
+ if (mProcessStartInitiatedTimestampMillis <= 0) {
+ return false;
+ }
+ return (SystemClock.uptimeMillis() - mProcessStartInitiatedTimestampMillis)
+ > constants.PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS;
+ }
+
+ /**
+ * Returns if the process start initiation is expected to be timed out at this point. This
+ * allows us to dump necessary state for debugging before the process start is timed out
+ * and discarded.
+ */
+ public boolean isProcessStartInitiationTimeoutExpected() {
+ if (mProcessStartInitiatedTimestampMillis <= 0) {
+ return false;
+ }
+ return (SystemClock.uptimeMillis() - mProcessStartInitiatedTimestampMillis)
+ > constants.PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS / 2;
+ }
+
+ public void clearProcessStartInitiatedTimestampMillis() {
+ mProcessStartInitiatedTimestampMillis = 0;
+ }
+
/**
* Get package name of the first application loaded into this process.
*/
@@ -810,7 +874,7 @@ class BroadcastProcessQueue {
* Return the broadcast being actively dispatched in this process.
*/
public @NonNull BroadcastRecord getActive() {
- return Objects.requireNonNull(mActive);
+ return Objects.requireNonNull(mActive, toString());
}
/**
@@ -818,7 +882,7 @@ class BroadcastProcessQueue {
* being actively dispatched in this process.
*/
public int getActiveIndex() {
- Objects.requireNonNull(mActive);
+ Objects.requireNonNull(mActive, toString());
return mActiveIndex;
}
@@ -1558,6 +1622,10 @@ class BroadcastProcessQueue {
if (mActiveReEnqueued) {
pw.print("activeReEnqueued:"); pw.println(mActiveReEnqueued);
}
+ if (mProcessStartInitiatedTimestampMillis > 0) {
+ pw.print("processStartInitiatedTimestamp:"); pw.println(
+ TimeUtils.formatUptime(mProcessStartInitiatedTimestampMillis));
+ }
}
@NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java b/services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java
new file mode 100644
index 000000000000..1bcedf6afd63
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.am;
+
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_PROCESSED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+final class BroadcastProcessedEventRecord {
+
+ /**
+ * Minimum threshold for logging the broadcast processed event.
+ */
+ private static final int MIN_THRESHOLD_FOR_LOGGING_TIME_MILLIS = 10;
+
+ @Nullable
+ private String mIntentAction;
+
+ private int mSenderUid;
+
+ private int mReceiverUid;
+
+ private int mNumberOfReceivers;
+
+ @NonNull
+ private String mReceiverProcessName;
+
+ private long mTotalBroadcastFinishTimeMillis;
+
+ private long mMaxReceiverFinishTimeMillis = Long.MIN_VALUE;
+
+ @NonNull
+ private int[] mBroadcastTypes;
+
+ @NonNull
+ public BroadcastProcessedEventRecord setBroadcastTypes(@NonNull int[] broadcastTypes) {
+ this.mBroadcastTypes = broadcastTypes;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setReceiverProcessName(
+ @NonNull String receiverProcessName) {
+ mReceiverProcessName = receiverProcessName;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setIntentAction(@Nullable String intentAction) {
+ mIntentAction = intentAction;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setSenderUid(int uid) {
+ mSenderUid = uid;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setReceiverUid(int uid) {
+ mReceiverUid = uid;
+ return this;
+ }
+
+ public void addReceiverFinishTime(long timeMillis) {
+ mTotalBroadcastFinishTimeMillis += timeMillis;
+ mMaxReceiverFinishTimeMillis = Math.max(mMaxReceiverFinishTimeMillis, timeMillis);
+ mNumberOfReceivers++;
+ }
+
+ @Nullable
+ String getIntentActionForTest() {
+ return mIntentAction;
+ }
+
+ int getSenderUidForTest() {
+ return mSenderUid;
+ }
+
+ int getReceiverUidForTest() {
+ return mReceiverUid;
+ }
+
+ int getNumberOfReceiversForTest() {
+ return mNumberOfReceivers;
+ }
+
+ @NonNull
+ String getReceiverProcessNameForTest() {
+ return mReceiverProcessName;
+ }
+
+ long getTotalBroadcastFinishTimeMillisForTest() {
+ return mTotalBroadcastFinishTimeMillis;
+ }
+
+ long getMaxReceiverFinishTimeMillisForTest() {
+ return mMaxReceiverFinishTimeMillis;
+ }
+
+ @NonNull
+ int[] getBroadcastTypesForTest() {
+ return mBroadcastTypes;
+ }
+
+ public void logToStatsD() {
+ // We do not care about the processes where total time to process the
+ // broadcast is less than 10ms/ are quick to process the broadcast.
+ if (mTotalBroadcastFinishTimeMillis <= MIN_THRESHOLD_FOR_LOGGING_TIME_MILLIS) {
+ return;
+ }
+
+ FrameworkStatsLog.write(
+ BROADCAST_PROCESSED,
+ mIntentAction,
+ mSenderUid,
+ mReceiverUid,
+ mNumberOfReceivers,
+ mReceiverProcessName,
+ mTotalBroadcastFinishTimeMillis,
+ mMaxReceiverFinishTimeMillis,
+ mBroadcastTypes);
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 78beb18263a7..6e893ad0a425 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -534,6 +534,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
// skip to look for another warm process
if (mRunningColdStart == null) {
mRunningColdStart = queue;
+ mRunningColdStart.clearProcessStartInitiatedTimestampMillis();
} else if (isPendingColdStartValid()) {
// Move to considering next runnable queue
queue = nextQueue;
@@ -542,6 +543,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
// Pending cold start is not valid, so clear it and move on.
clearInvalidPendingColdStart();
mRunningColdStart = queue;
+ mRunningColdStart.clearProcessStartInitiatedTimestampMillis();
}
}
@@ -588,7 +590,9 @@ class BroadcastQueueImpl extends BroadcastQueue {
@GuardedBy("mService")
private boolean isPendingColdStartValid() {
- if (mRunningColdStart.app.getPid() > 0) {
+ if (mRunningColdStart.hasProcessStartInitiationTimedout()) {
+ return false;
+ } else if (mRunningColdStart.app.getPid() > 0) {
// If the process has already started, check if it wasn't killed.
return !mRunningColdStart.app.isKilled();
} else {
@@ -606,8 +610,9 @@ class BroadcastQueueImpl extends BroadcastQueue {
} else {
mRunningColdStart.reEnqueueActiveBroadcast();
}
- demoteFromRunningLocked(mRunningColdStart);
+ final BroadcastProcessQueue queue = mRunningColdStart;
clearRunningColdStart();
+ demoteFromRunningLocked(queue);
enqueueUpdateRunningList();
}
@@ -672,6 +677,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
if ((mRunningColdStart != null) && (mRunningColdStart == queue)) {
// We've been waiting for this app to cold start, and it's ready
// now; dispatch its next broadcast and clear the slot
+ mRunningColdStart.clearProcessStartInitiatedTimestampMillis();
mRunningColdStart = null;
// Now that we're running warm, we can finally request that OOM
@@ -755,6 +761,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
// We've been waiting for this app to cold start, and it had
// trouble; clear the slot and fail delivery below
+ mRunningColdStart.clearProcessStartInitiatedTimestampMillis();
mRunningColdStart = null;
// We might be willing to kick off another cold start
@@ -1035,6 +1042,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
"startProcessLocked failed");
return true;
}
+ queue.setProcessStartInitiatedTimestampMillis(SystemClock.uptimeMillis());
// TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
startTimeNs, queue.app, r.getReceiverIntent(receiver), r.alarm /* isAlarm */);
@@ -1527,6 +1535,15 @@ class BroadcastQueueImpl extends BroadcastQueue {
final int cookie = traceBegin("demoteFromRunning");
// We've drained running broadcasts; maybe move back to runnable
+ if (mRunningColdStart == queue) {
+ // TODO: b/399020479 - Remove wtf log once we identify the case where mRunningColdStart
+ // is not getting cleared.
+ // If this queue is mRunningColdStart, then it should have been cleared before
+ // it is demoted. Log a wtf if this isn't the case.
+ Slog.wtf(TAG, "mRunningColdStart has not been cleared; mRunningColdStart.app: "
+ + mRunningColdStart.app + " , queue.app: " + queue.app,
+ new IllegalStateException());
+ }
queue.makeActiveIdle();
queue.traceProcessEnd();
@@ -1981,6 +1998,32 @@ class BroadcastQueueImpl extends BroadcastQueue {
if (mRunningColdStart != null) {
checkState(getRunningIndexOf(mRunningColdStart) >= 0,
"isOrphaned " + mRunningColdStart);
+
+ final BroadcastProcessQueue queue = getProcessQueue(mRunningColdStart.processName,
+ mRunningColdStart.uid);
+ checkState(queue == mRunningColdStart, "Conflicting " + mRunningColdStart
+ + " with queue " + queue
+ + ";\n mRunningColdStart.app: " + mRunningColdStart.app.toDetailedString()
+ + ";\n queue.app: " + queue.app.toDetailedString());
+
+ checkState(mRunningColdStart.app != null, "Empty cold start queue "
+ + mRunningColdStart);
+
+ if (mRunningColdStart.isProcessStartInitiationTimeoutExpected()) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Process start timeout expected for app ");
+ sb.append(mRunningColdStart.app);
+ sb.append(" in queue ");
+ sb.append(mRunningColdStart);
+ sb.append("; startUpTime: ");
+ final long startupTimeMs =
+ mRunningColdStart.getProcessStartInitiatedTimestampMillis();
+ sb.append(startupTimeMs == 0 ? "<none>"
+ : TimeUtils.formatDuration(startupTimeMs - SystemClock.uptimeMillis()));
+ sb.append(";\n app: ");
+ sb.append(mRunningColdStart.app.toDetailedString());
+ checkState(false, sb.toString());
+ }
}
// Verify health of all known process queues
@@ -2080,7 +2123,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
@GuardedBy("mService")
private void notifyStartedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
- queue.app.mReceivers.incrementCurReceivers();
+ queue.incrementCurAppReceivers();
// Don't bump its LRU position if it's in the background restricted.
if (mService.mInternal.getRestrictionLevel(
@@ -2105,7 +2148,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
@GuardedBy("mService")
private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
- queue.app.mReceivers.decrementCurReceivers();
+ queue.decrementCurAppReceivers();
if (queue.runningOomAdjusted) {
mService.enqueueOomAdjTargetLocked(queue.app);
@@ -2189,6 +2232,11 @@ class BroadcastQueueImpl extends BroadcastQueue {
logBroadcastDeliveryEventReported(queue, app, r, index, receiver);
}
+ if (!r.isAssumedDelivered(index) && r.wasDelivered(index)) {
+ r.updateBroadcastProcessedEventRecord(receiver,
+ r.terminalTime[index] - r.scheduledTime[index]);
+ }
+
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
notifyFinishBroadcast(r);
@@ -2254,6 +2302,7 @@ class BroadcastQueueImpl extends BroadcastQueue {
mHistory.onBroadcastFinishedLocked(r);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ r.logBroadcastProcessedEventRecord();
if (r.intent.getComponent() == null && r.intent.getPackage() == null
&& (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
@@ -2326,12 +2375,6 @@ class BroadcastQueueImpl extends BroadcastQueue {
@VisibleForTesting
@GuardedBy("mService")
- @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) {
- return removeProcessQueue(app.processName, app.info.uid);
- }
-
- @VisibleForTesting
- @GuardedBy("mService")
@Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName,
int uid) {
BroadcastProcessQueue prev = null;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c1b0a76b24af..45c7d59b2392 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -167,6 +167,12 @@ final class BroadcastRecord extends Binder {
@Nullable
private ArrayMap<BroadcastRecord, Boolean> mMatchingRecordsCache;
+ // Stores the {@link BroadcastProcessedEventRecord} for each process associated with this
+ // record.
+ @NonNull
+ private ArrayMap<String, BroadcastProcessedEventRecord> mBroadcastProcessedRecords =
+ new ArrayMap<>();
+
private @Nullable String mCachedToString;
private @Nullable String mCachedToShortString;
@@ -654,6 +660,17 @@ final class BroadcastRecord extends Binder {
}
}
+ boolean wasDelivered(int index) {
+ final int deliveryState = getDeliveryState(index);
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ case DELIVERY_TIMEOUT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
void copyEnqueueTimeFrom(@NonNull BroadcastRecord replacedBroadcast) {
originalEnqueueClockTime = enqueueClockTime;
enqueueTime = replacedBroadcast.enqueueTime;
@@ -1327,4 +1344,48 @@ final class BroadcastRecord extends Binder {
proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction());
proto.end(token);
}
+
+ /**
+ * Uses the {@link BroadcastProcessedEventRecord} pojo to store the logging information related
+ * to {@param receiver} object.
+ */
+ public void updateBroadcastProcessedEventRecord(@NonNull Object receiver, long timeMillis) {
+ if (!Flags.logBroadcastProcessedEvent()) {
+ return;
+ }
+
+ final String receiverProcessName = getReceiverProcessName(receiver);
+ BroadcastProcessedEventRecord broadcastProcessedEventRecord =
+ mBroadcastProcessedRecords.get(receiverProcessName);
+ if (broadcastProcessedEventRecord == null) {
+ broadcastProcessedEventRecord = new BroadcastProcessedEventRecord()
+ .setBroadcastTypes(calculateTypesForLogging())
+ .setIntentAction(intent.getAction())
+ .setReceiverProcessName(receiverProcessName)
+ .setReceiverUid(getReceiverUid(receiver))
+ .setSenderUid(callingUid);
+
+ mBroadcastProcessedRecords.put(receiverProcessName, broadcastProcessedEventRecord);
+ }
+
+ broadcastProcessedEventRecord.addReceiverFinishTime(timeMillis);
+ }
+
+ public void logBroadcastProcessedEventRecord() {
+ if (!Flags.logBroadcastProcessedEvent()) {
+ return;
+ }
+
+ int size = mBroadcastProcessedRecords.size();
+ for (int i = 0; i < size; i++) {
+ mBroadcastProcessedRecords.valueAt(i).logToStatsD();
+ }
+ mBroadcastProcessedRecords.clear();
+ }
+
+ @VisibleForTesting
+ @NonNull
+ ArrayMap<String, BroadcastProcessedEventRecord> getBroadcastProcessedRecordsForTest() {
+ return mBroadcastProcessedRecords;
+ }
}
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 31704c442290..4e1d77c26129 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -142,6 +142,10 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
| Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS);
}
+ @Override
+ public boolean transmitsCpuTime() {
+ return !hasFlag(Context.BIND_ALLOW_FREEZE);
+ }
public long getFlags() {
return flags;
@@ -273,6 +277,9 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
if (hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
sb.append("CAPS ");
}
+ if (hasFlag(Context.BIND_ALLOW_FREEZE)) {
+ sb.append("!CPU ");
+ }
if (serviceDead) {
sb.append("DEAD ");
}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 4b6d6bc955cc..fd2d8352eb4f 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -61,7 +61,7 @@ per-file *Oom* = file:/OOM_ADJUSTER_OWNERS
per-file ProcessStateController.java = file:/OOM_ADJUSTER_OWNERS
# Miscellaneous
-per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com
+per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com
per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS
# Activity Security
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 13d367a95942..fa35da30bf4b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2802,7 +2802,7 @@ public class OomAdjuster {
// we check the final procstate, and remove it if the procsate is below BFGS.
capability |= getBfslCapabilityFromClient(client);
- capability |= getCpuCapabilityFromClient(client);
+ capability |= getCpuCapabilityFromClient(cr, client);
if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
@@ -3259,7 +3259,7 @@ public class OomAdjuster {
// we check the final procstate, and remove it if the procsate is below BFGS.
capability |= getBfslCapabilityFromClient(client);
- capability |= getCpuCapabilityFromClient(client);
+ capability |= getCpuCapabilityFromClient(conn, client);
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
// If the other app is cached for any reason, for purposes here
@@ -3433,8 +3433,12 @@ public class OomAdjuster {
// Process has user visible activities.
return PROCESS_CAPABILITY_CPU_TIME;
}
- if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
- // It running a short fgs, just give it cpu time.
+ if (Flags.prototypeAggressiveFreezing()) {
+ if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
+ // Grant cpu time for short FGS even when aggressively freezing.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ } else if (app.mServices.hasForegroundServices()) {
return PROCESS_CAPABILITY_CPU_TIME;
}
if (app.mReceivers.numberOfCurReceivers() > 0) {
@@ -3498,10 +3502,13 @@ public class OomAdjuster {
/**
* @return the CPU capability from a client (of a service binding or provider).
*/
- private static int getCpuCapabilityFromClient(ProcessRecord client) {
- // Just grant CPU capability every time
- // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings.
- return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME;
+ private static int getCpuCapabilityFromClient(OomAdjusterModernImpl.Connection conn,
+ ProcessRecord client) {
+ if (conn == null || conn.transmitsCpuTime()) {
+ return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME;
+ } else {
+ return 0;
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 1b7e8f0bd244..7e7b5685cf13 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -635,6 +635,15 @@ public class OomAdjusterModernImpl extends OomAdjuster {
* Returns true if this connection can propagate capabilities.
*/
boolean canAffectCapabilities();
+
+ /**
+ * Returns whether this connection transmits PROCESS_CAPABILITY_CPU_TIME to the host, if the
+ * client possesses it.
+ */
+ default boolean transmitsCpuTime() {
+ // Always lend this capability by default.
+ return true;
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 2c0366e6a6db..ac30be99979e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -31,7 +31,6 @@ import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.window.flags.Flags.balClearAllowlistDuration;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -330,7 +329,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
mAllowBgActivityStartsForActivitySender.remove(token);
mAllowBgActivityStartsForBroadcastSender.remove(token);
mAllowBgActivityStartsForServiceSender.remove(token);
- if (mAllowlistDuration != null && balClearAllowlistDuration()) {
+ if (mAllowlistDuration != null) {
TempAllowListDuration duration = mAllowlistDuration.get(token);
if (duration != null
&& duration.type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 1503d889c298..400c699bf93f 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -70,6 +70,7 @@ import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -1414,6 +1415,16 @@ class ProcessRecord implements WindowProcessListener {
return mStringName = sb.toString();
}
+ String toDetailedString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this);
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+ dump(pw, " ");
+ sb.append(sw);
+ return sb.toString();
+ }
+
/*
* Return true if package has been added false if not
*/
@@ -1443,17 +1454,32 @@ class ProcessRecord implements WindowProcessListener {
void onProcessFrozen() {
mProfile.onProcessFrozen();
- if (mThread != null) mThread.onProcessPaused();
+ final ApplicationThreadDeferred t;
+ synchronized (mService) {
+ t = mThread;
+ }
+ // Release the lock before calling the notifier, in case that calls back into AM.
+ if (t != null) t.onProcessPaused();
}
void onProcessUnfrozen() {
- if (mThread != null) mThread.onProcessUnpaused();
+ final ApplicationThreadDeferred t;
+ synchronized (mService) {
+ t = mThread;
+ }
+ // Release the lock before calling the notifier, in case that calls back into AM.
+ if (t != null) t.onProcessUnpaused();
mProfile.onProcessUnfrozen();
mServices.onProcessUnfrozen();
}
void onProcessFrozenCancelled() {
- if (mThread != null) mThread.onProcessPausedCancelled();
+ final ApplicationThreadDeferred t;
+ synchronized (mService) {
+ t = mThread;
+ }
+ // Release the lock before calling the notifier, in case that calls back into AM.
+ if (t != null) t.onProcessPausedCancelled();
mServices.onProcessFrozenCancelled();
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3a041fd3b38a..cb4342f27bc8 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -187,10 +187,12 @@ public class SettingsToPropertiesMapper {
"crumpet",
"dck_framework",
"desktop_apps",
+ "desktop_audio",
"desktop_better_together",
"desktop_bsp",
"desktop_camera",
"desktop_connectivity",
+ "desktop_dev_experience",
"desktop_display",
"desktop_commercial",
"desktop_firmware",
@@ -199,10 +201,14 @@ public class SettingsToPropertiesMapper {
"desktop_input",
"desktop_kernel",
"desktop_ml",
+ "desktop_networking",
"desktop_serviceability",
"desktop_oobe",
"desktop_peripherals",
+ "desktop_personalization",
"desktop_pnp",
+ "desktop_privacy",
+ "desktop_release",
"desktop_security",
"desktop_stats",
"desktop_sysui",
@@ -247,11 +253,12 @@ public class SettingsToPropertiesMapper {
"pixel_state_server",
"pixel_system_sw_video",
"pixel_video_sw",
+ "pixel_vpn",
"pixel_watch",
+ "pixel_watch_debug_trace",
"pixel_wifi",
"platform_compat",
"platform_security",
- "pixel_watch_debug_trace",
"pmw",
"power",
"preload_safety",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 18f3500b2d56..40a9bbec3598 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -340,16 +340,16 @@ class UserController implements Handler.Callback {
private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks;
/**
- * Messages for switching from {@link android.os.UserHandle#SYSTEM}.
+ * Message shown when switching from a user.
*/
@GuardedBy("mLock")
- private String mSwitchingFromSystemUserMessage;
+ private final SparseArray<String> mSwitchingFromUserMessage = new SparseArray<>();
/**
- * Messages for switching to {@link android.os.UserHandle#SYSTEM}.
+ * Message shown when switching to a user.
*/
@GuardedBy("mLock")
- private String mSwitchingToSystemUserMessage;
+ private final SparseArray<String> mSwitchingToUserMessage = new SparseArray<>();
/**
* Callbacks that are still active after {@link #getUserSwitchTimeoutMs}
@@ -2271,8 +2271,8 @@ class UserController implements Handler.Callback {
private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
- getSwitchingFromSystemUserMessageUnchecked(),
- getSwitchingToSystemUserMessageUnchecked(),
+ getSwitchingFromUserMessageUnchecked(fromToUserPair.first.id),
+ getSwitchingToUserMessageUnchecked(fromToUserPair.second.id),
/* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id));
}
@@ -3388,41 +3388,45 @@ class UserController implements Handler.Callback {
return mLockPatternUtils.isLockScreenDisabled(userId);
}
- void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) {
+ void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message) {
synchronized (mLock) {
- mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
+ mSwitchingFromUserMessage.put(user, message);
}
}
- void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) {
+ void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message) {
synchronized (mLock) {
- mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
+ mSwitchingToUserMessage.put(user, message);
}
}
// Called by AMS, must check permission
- String getSwitchingFromSystemUserMessage() {
- checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()");
+ @Nullable
+ String getSwitchingFromUserMessage(@UserIdInt int userId) {
+ checkHasManageUsersPermission("getSwitchingFromUserMessage()");
- return getSwitchingFromSystemUserMessageUnchecked();
+ return getSwitchingFromUserMessageUnchecked(userId);
}
// Called by AMS, must check permission
- String getSwitchingToSystemUserMessage() {
- checkHasManageUsersPermission("getSwitchingToSystemUserMessage()");
+ @Nullable
+ String getSwitchingToUserMessage(@UserIdInt int userId) {
+ checkHasManageUsersPermission("getSwitchingToUserMessage()");
- return getSwitchingToSystemUserMessageUnchecked();
+ return getSwitchingToUserMessageUnchecked(userId);
}
- private String getSwitchingFromSystemUserMessageUnchecked() {
+ @Nullable
+ private String getSwitchingFromUserMessageUnchecked(@UserIdInt int userId) {
synchronized (mLock) {
- return mSwitchingFromSystemUserMessage;
+ return mSwitchingFromUserMessage.get(userId);
}
}
- private String getSwitchingToSystemUserMessageUnchecked() {
+ @Nullable
+ private String getSwitchingToUserMessageUnchecked(@UserIdInt int userId) {
synchronized (mLock) {
- return mSwitchingToSystemUserMessage;
+ return mSwitchingToUserMessage.get(userId);
}
}
@@ -3518,12 +3522,8 @@ class UserController implements Handler.Callback {
+ mIsBroadcastSentForSystemUserStarted);
pw.println(" mIsBroadcastSentForSystemUserStarting:"
+ mIsBroadcastSentForSystemUserStarting);
- if (mSwitchingFromSystemUserMessage != null) {
- pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage);
- }
- if (mSwitchingToSystemUserMessage != null) {
- pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
- }
+ pw.println(" mSwitchingFromUserMessage:" + mSwitchingFromUserMessage);
+ pw.println(" mSwitchingToUserMessage:" + mSwitchingToUserMessage);
pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
}
}
@@ -4046,7 +4046,7 @@ class UserController implements Handler.Callback {
}
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
- String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+ @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage,
@NonNull Runnable onShown) {
if (mService.mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
@@ -4059,7 +4059,7 @@ class UserController implements Handler.Callback {
synchronized (mUserSwitchingDialogLock) {
dismissUserSwitchingDialog(null);
mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
- mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage);
+ mHandler, switchingFromUserMessage, switchingToUserMessage);
mUserSwitchingDialog.show(onShown);
}
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 223e0b79ec0b..f4e733a0c99f 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -52,6 +52,7 @@ import com.android.internal.R;
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.UserIcons;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -75,21 +76,23 @@ class UserSwitchingDialog extends Dialog {
protected final UserInfo mOldUser;
protected final UserInfo mNewUser;
- private final String mSwitchingFromSystemUserMessage;
- private final String mSwitchingToSystemUserMessage;
+ @Nullable
+ private final String mSwitchingFromUserMessage;
+ @Nullable
+ private final String mSwitchingToUserMessage;
protected final Context mContext;
private final int mTraceCookie;
UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler,
- String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+ @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage) {
super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
mContext = context;
mOldUser = oldUser;
mNewUser = newUser;
mHandler = handler;
- mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
- mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
+ mSwitchingFromUserMessage = switchingFromUserMessage;
+ mSwitchingToUserMessage = switchingToUserMessage;
mDisableAnimations = SystemProperties.getBoolean(
"debug.usercontroller.disable_user_switching_dialog_animations", false);
mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
@@ -166,14 +169,14 @@ class UserSwitchingDialog extends Dialog {
: R.string.demo_starting_message);
}
- final String message =
- mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage
- : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null;
+ if (mSwitchingFromUserMessage != null || mSwitchingToUserMessage != null) {
+ if (mSwitchingFromUserMessage != null && mSwitchingToUserMessage != null) {
+ return mSwitchingFromUserMessage + " " + mSwitchingToUserMessage;
+ }
+ return Objects.requireNonNullElse(mSwitchingFromUserMessage, mSwitchingToUserMessage);
+ }
- return message != null ? message
- // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null,
- // fallback to system message.
- : res.getString(R.string.user_switching_message, mNewUser.name);
+ return res.getString(R.string.user_switching_message, mNewUser.name);
}
@Override
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
index 68e21a35a531..3867f77e408a 100644
--- a/services/core/java/com/android/server/am/broadcasts_flags.aconfig
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -26,4 +26,15 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "log_broadcast_processed_event"
+ namespace: "backstage_power"
+ description: "Log the broadcast processed event to Statsd"
+ bug: "387576580"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index f7d7ed541780..fb33cb1ff8c0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -367,7 +367,7 @@ public class AppOpsService extends IAppOpsService.Stub {
private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW);
- volatile @NonNull HistoricalRegistry mHistoricalRegistry;
+ volatile @NonNull HistoricalRegistryInterface mHistoricalRegistry;
/*
* These are app op restrictions imposed per user from various parties.
@@ -1056,7 +1056,11 @@ public class AppOpsService extends IAppOpsService.Stub {
AppOpsManager.invalidateAppOpModeCache();
AppOpsManager.disableAppOpModeCache();
- mHistoricalRegistry = new HistoricalRegistry(this, context);
+ if (Flags.enableAllSqliteAppopsAccesses()) {
+ mHistoricalRegistry = new HistoricalRegistrySql(context);
+ } else {
+ mHistoricalRegistry = new HistoricalRegistry(this, context);
+ }
}
public void publish() {
@@ -1424,7 +1428,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@GuardedBy("this")
private void packageRemovedLocked(int uid, String packageName) {
- getIoHandler().post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ getIoHandler().post(PooledLambda.obtainRunnable(HistoricalRegistryInterface::clearHistory,
mHistoricalRegistry, uid, packageName));
UidState uidState = mUidStates.get(uid);
@@ -1992,10 +1996,12 @@ public class AppOpsService extends IAppOpsService.Stub {
new String[attributionChainExemptPackages.size()]) : null;
// Must not hold the appops lock
- getIoHandler().post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ getIoHandler().post(PooledLambda.obtainRunnable(
+ HistoricalRegistryInterface::getHistoricalOps,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
+ callback
+ ).recycleOnUse());
}
@Override
@@ -2024,7 +2030,7 @@ public class AppOpsService extends IAppOpsService.Stub {
// Must not hold the appops lock
getIoHandler().post(PooledLambda.obtainRunnable(
- HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ HistoricalRegistryInterface::getHistoricalOpsFromDiskRaw,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
callback).recycleOnUse());
@@ -6961,7 +6967,6 @@ public class AppOpsService extends IAppOpsService.Stub {
offsetHistory_enforcePermission();
// Must not hold the appops lock
mHistoricalRegistry.offsetHistory(offsetMillis);
- mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
@@ -7002,7 +7007,13 @@ public class AppOpsService extends IAppOpsService.Stub {
SystemClock.sleep(offlineDurationMillis);
}
- mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+ if (Flags.enableAllSqliteAppopsAccesses()) {
+ mHistoricalRegistry = new HistoricalRegistrySql(
+ (HistoricalRegistrySql) mHistoricalRegistry);
+ } else {
+ mHistoricalRegistry = new HistoricalRegistry((HistoricalRegistry) mHistoricalRegistry);
+ }
+
mHistoricalRegistry.systemReady(mContext.getContentResolver());
mHistoricalRegistry.persistPendingHistory();
}
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 9dd09cef88f9..b9bc27d9c696 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -113,7 +113,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
- DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
+ accessCount);
}
/**
@@ -163,7 +163,7 @@ final class AttributedOp {
public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
rejected(System.currentTimeMillis(), uidState, flags);
- mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid,
+ mAppOpsService.mHistoricalRegistry.incrementOpRejectedCount(parent.op, parent.uid,
parent.packageName, tag, uidState, flags);
}
@@ -257,8 +257,7 @@ final class AttributedOp {
if (isStarted) {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
- attributionFlags, attributionChainId,
- DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1);
+ attributionFlags, attributionChainId, 1);
}
}
@@ -344,9 +343,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
- event.getAttributionFlags(), event.getAttributionChainId(),
- isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP
- : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP);
+ event.getAttributionFlags(), event.getAttributionChainId());
if (!isPausing) {
mAppOpsService.mInProgressStartOpEventPool.release(event);
@@ -454,7 +451,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), startTime, event.getAttributionFlags(),
- event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1);
+ event.getAttributionChainId(), 1);
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, event.getVirtualDeviceId(), true,
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index 86f5d9bd637f..3a8d5835ccb1 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -24,6 +24,7 @@ import android.database.DatabaseErrorHandler;
import android.database.DefaultDatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteRawStatement;
import android.os.Environment;
@@ -174,11 +175,16 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
if (DEBUG) {
Slog.i(LOG_TAG, "DB execSQL, sql: " + sql);
}
- SQLiteDatabase db = getWritableDatabase();
- if (bindArgs == null) {
- db.execSQL(sql);
- } else {
- db.execSQL(sql, bindArgs);
+ try {
+ SQLiteDatabase db = getWritableDatabase();
+ if (bindArgs == null) {
+ db.execSQL(sql);
+ } else {
+ db.execSQL(sql, bindArgs);
+ }
+ } catch (SQLiteFullException exception) {
+ Slog.e(LOG_TAG, "Couldn't exec sql command, disk is full. Discrete ops "
+ + "db file size (bytes) : " + getDatabaseFile().length(), exception);
}
}
@@ -189,11 +195,11 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
@AppOpsManager.HistoricalOpsRequestFilter int requestFilters,
int uidFilter, @Nullable String packageNameFilter,
@Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter,
- long beginTime, long endTime, int limit, String orderByColumn) {
+ long beginTime, long endTime, int limit, String orderByColumn, boolean ascending) {
List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters,
uidFilter, packageNameFilter,
attributionTagFilter, opCodesFilter, opFlagsFilter);
- String sql = buildSql(conditions, orderByColumn, limit);
+ String sql = buildSql(conditions, orderByColumn, ascending, limit);
long startTime = 0;
if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
startTime = SystemClock.elapsedRealtime();
@@ -249,7 +255,8 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
return results;
}
- private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) {
+ private String buildSql(List<SQLCondition> conditions, String orderByColumn, boolean ascending,
+ int limit) {
StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA);
if (!conditions.isEmpty()) {
sql.append(" WHERE ");
@@ -264,6 +271,7 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
if (orderByColumn != null) {
sql.append(" ORDER BY ").append(orderByColumn);
+ sql.append(ascending ? " ASC " : " DESC ");
}
if (limit > 0) {
sql.append(" LIMIT ").append(limit);
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
index c38ee55b4f42..29e78be93da9 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
@@ -40,7 +40,16 @@ public class DiscreteOpsMigrationHelper {
static void migrateDiscreteOpsToXml(DiscreteOpsSqlRegistry sqlRegistry,
DiscreteOpsXmlRegistry xmlRegistry) {
List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps();
- DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps);
+
+ // Only migrate configured discrete ops. Sqlite may contain all runtime ops, and more.
+ List<DiscreteOpsSqlRegistry.DiscreteOp> filteredList = new ArrayList<>();
+ for (DiscreteOpsSqlRegistry.DiscreteOp opEvent: sqlOps) {
+ if (DiscreteOpsRegistry.isDiscreteOp(opEvent.getOpCode(), opEvent.getOpFlags())) {
+ filteredList.add(opEvent);
+ }
+ }
+
+ DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(filteredList);
xmlRegistry.migrateSqliteData(xmlOps);
sqlRegistry.deleteDatabase();
}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
index 12c35ae92cbe..70b7016fbb90 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
@@ -16,6 +16,9 @@
package com.android.server.appop;
+import static android.app.AppOpsManager.OP_ACCESS_ACCESSIBILITY;
+import static android.app.AppOpsManager.OP_ACCESS_NOTIFICATIONS;
+import static android.app.AppOpsManager.OP_BIND_ACCESSIBILITY_SERVICE;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
@@ -23,30 +26,24 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
+import static android.app.AppOpsManager.OP_GPS;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
-import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
+import static android.app.AppOpsManager.OP_READ_DEVICE_IDENTIFIERS;
import static android.app.AppOpsManager.OP_READ_HEART_RATE;
-import static android.app.AppOpsManager.OP_READ_ICC_SMS;
import static android.app.AppOpsManager.OP_READ_OXYGEN_SATURATION;
import static android.app.AppOpsManager.OP_READ_SKIN_TEMPERATURE;
-import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
-import static android.app.AppOpsManager.OP_SEND_SMS;
-import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
-import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND;
import static java.lang.Long.min;
import static java.lang.Math.max;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -54,16 +51,13 @@ import android.os.AsyncTask;
import android.os.Build;
import android.permission.flags.Flags;
import android.provider.DeviceConfig;
+import android.util.IntArray;
import android.util.Slog;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
-
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.time.Duration;
+import java.util.Arrays;
import java.util.Date;
import java.util.Set;
@@ -100,21 +94,37 @@ abstract class DiscreteOpsRegistry {
static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
"discrete_history_quantization_millis";
static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
+ // Comma separated app ops list config for testing i.e. "1,2,3,4"
static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
- static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
+ // These ops are deemed important for detecting a malicious app, and are recorded.
+ static final int[] IMPORTANT_OPS_FOR_SECURITY = new int[] {
+ OP_GPS,
+ OP_ACCESS_NOTIFICATIONS,
+ OP_RUN_IN_BACKGROUND,
+ OP_BIND_ACCESSIBILITY_SERVICE,
+ OP_ACCESS_ACCESSIBILITY,
+ OP_READ_DEVICE_IDENTIFIERS,
+ OP_MONITOR_HIGH_POWER_LOCATION,
+ OP_MONITOR_LOCATION
+ };
+
+ // These are additional ops, which are not backed by runtime permissions, but are recorded.
+ static final int[] ADDITIONAL_DISCRETE_OPS = new int[] {
+ OP_PHONE_CALL_MICROPHONE,
+ OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+ OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ OP_PHONE_CALL_CAMERA,
+ OP_EMERGENCY_LOCATION,
+ OP_RESERVED_FOR_TESTING
+ };
+
+ // Legacy ops captured in discrete database.
+ private static final String LEGACY_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
+ "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
+ OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+ OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ "," + OP_READ_HEART_RATE + "," + OP_READ_OXYGEN_SATURATION + ","
+ OP_READ_SKIN_TEMPERATURE + "," + OP_RESERVED_FOR_TESTING;
- static final int[] sDiscreteOpsToLog =
- new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
- OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
- OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
- OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
- OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
- OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
- };
static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
@@ -126,7 +136,7 @@ abstract class DiscreteOpsRegistry {
// in case of duplicate op events.
static long sDiscreteHistoryQuantization;
- static int[] sDiscreteOps;
+ static int[] sDiscreteOps = new int[0];
static int sDiscreteFlags;
static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
@@ -134,27 +144,6 @@ abstract class DiscreteOpsRegistry {
boolean mDebugMode = false;
- static final int ACCESS_TYPE_NOTE_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
- static final int ACCESS_TYPE_START_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
- static final int ACCESS_TYPE_FINISH_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
- static final int ACCESS_TYPE_PAUSE_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
- static final int ACCESS_TYPE_RESUME_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
- ACCESS_TYPE_NOTE_OP,
- ACCESS_TYPE_START_OP,
- ACCESS_TYPE_FINISH_OP,
- ACCESS_TYPE_PAUSE_OP,
- ACCESS_TYPE_RESUME_OP
- })
- @interface AccessType {}
-
void systemReady() {
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
@@ -166,8 +155,7 @@ abstract class DiscreteOpsRegistry {
abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId,
int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
- @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteOpsRegistry.AccessType int accessType);
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId);
/**
* A periodic callback from {@link AppOpsService} to flush the in memory events to disk.
@@ -218,7 +206,7 @@ abstract class DiscreteOpsRegistry {
}
static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
- if (!ArrayUtils.contains(sDiscreteOps, op)) {
+ if (Arrays.binarySearch(sDiscreteOps, op) < 0) {
return false;
}
if ((flags & (sDiscreteFlags)) == 0) {
@@ -227,9 +215,6 @@ abstract class DiscreteOpsRegistry {
return true;
}
- // could this be impl detail of discrete registry, just one test is using the method
- // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps();
-
private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
@@ -251,11 +236,42 @@ abstract class DiscreteOpsRegistry {
} else {
sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION;
}
- sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags =
- p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
- sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList(
- p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList(
- DEFAULT_DISCRETE_OPS);
+ sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS)
+ ? p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
+ String opsListConfig = p.getString(PROPERTY_DISCRETE_OPS_LIST, null);
+ sDiscreteOps = opsListConfig == null ? getDefaultOpsList() : parseOpsList(opsListConfig);
+
+ Arrays.sort(sDiscreteOps);
+ }
+
+ // App ops backed by runtime/dangerous permissions.
+ private static IntArray getRuntimePermissionOps() {
+ IntArray runtimeOps = new IntArray();
+ for (int op = 0; op < AppOpsManager._NUM_OP; op++) {
+ if (AppOpsManager.opIsRuntimePermission(op)) {
+ runtimeOps.add(op);
+ }
+ }
+ return runtimeOps;
+ }
+
+ /**
+ * @return an array of app ops captured into discrete database.
+ */
+ private static int[] getDefaultOpsList() {
+ if (!(Flags.recordAllRuntimeAppopsSqlite() && Flags.enableSqliteAppopsAccesses())) {
+ return getDefaultLegacyOps();
+ }
+
+ IntArray discreteOpsArray = getRuntimePermissionOps();
+ discreteOpsArray.addAll(IMPORTANT_OPS_FOR_SECURITY);
+ discreteOpsArray.addAll(ADDITIONAL_DISCRETE_OPS);
+
+ return discreteOpsArray.toArray();
+ }
+
+ private static int[] getDefaultLegacyOps() {
+ return parseOpsList(LEGACY_OPS);
}
private static int[] parseOpsList(String opsList) {
@@ -273,32 +289,8 @@ abstract class DiscreteOpsRegistry {
}
} catch (NumberFormatException e) {
Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
- return parseOpsList(DEFAULT_DISCRETE_OPS);
+ return getDefaultOpsList();
}
return result;
}
-
- /**
- * Whether app op access tacking is enabled and a metric event should be logged.
- */
- static boolean shouldLogAccess(int op) {
- return Flags.appopAccessTrackingLoggingEnabled()
- && ArrayUtils.contains(sDiscreteOpsToLog, op);
- }
-
- String getAttributionTag(String attributionTag, String packageName) {
- if (attributionTag == null || packageName == null) {
- return attributionTag;
- }
- int firstChar = 0;
- if (attributionTag.startsWith(packageName)) {
- firstChar = packageName.length();
- if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
- == '.') {
- firstChar++;
- }
- }
- return attributionTag.substring(firstChar);
- }
-
}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
index dc11be9aadb6..f50f45a182d0 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
@@ -36,7 +36,6 @@ import android.util.IntArray;
import android.util.LongSparseArray;
import android.util.Slog;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
import java.io.File;
@@ -97,15 +96,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
void recordDiscreteAccess(int uid, String packageName,
@NonNull String deviceId, int op,
@Nullable String attributionTag, int flags, int uidState,
- long accessTime, long accessDuration, int attributionFlags, int attributionChainId,
- int accessType) {
- if (shouldLogAccess(op)) {
- FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
- uidState, flags, attributionFlags,
- getAttributionTag(attributionTag, packageName),
- attributionChainId);
- }
-
+ long accessTime, long accessDuration, int attributionFlags, int attributionChainId) {
if (!isDiscreteOp(op, flags)) {
return;
}
@@ -127,7 +118,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
@Override
void shutdown() {
mSqliteWriteHandler.removeAllPendingMessages();
- mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAllAppOpEvents());
}
@Override
@@ -181,15 +172,19 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
@Nullable String[] opNamesFilter,
@Nullable String attributionTagFilter, int opFlagsFilter,
Set<String> attributionExemptPkgs) {
+ IntArray opCodes = getAppOpCodes(filter, opNamesFilter);
// flush the cache into database before read.
- mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
+ if (opCodes != null) {
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAppOpEvents(opCodes));
+ } else {
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAllAppOpEvents());
+ }
boolean assembleChains = attributionExemptPkgs != null;
- IntArray opCodes = getAppOpCodes(filter, opNamesFilter);
beginTimeMillis = Math.max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff,
ChronoUnit.MILLIS).toEpochMilli());
List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter,
packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis,
- endTimeMillis, -1, null);
+ endTimeMillis, -1, null, false);
LongSparseArray<AttributionChain> attributionChains = null;
if (assembleChains) {
@@ -222,14 +217,15 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
@AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
int nDiscreteOps) {
- writeAndClearOldAccessHistory();
+ // flush the cache into database before dump.
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.evictAllAppOpEvents());
IntArray opCodes = new IntArray();
if (dumpOp != AppOpsManager.OP_NONE) {
opCodes.add(dumpOp);
}
List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter,
packageNameFilter, attributionTagFilter, opCodes, 0, -1,
- -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME);
+ -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME, false);
pw.print(prefix);
pw.print("Largest chain id: ");
@@ -374,7 +370,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
try {
List<DiscreteOp> evictedEvents;
synchronized (mDiscreteOpCache) {
- evictedEvents = mDiscreteOpCache.evict();
+ evictedEvents = mDiscreteOpCache.evictOldAppOpEvents();
}
mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
} finally {
@@ -397,7 +393,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
try {
List<DiscreteOp> evictedEvents;
synchronized (mDiscreteOpCache) {
- evictedEvents = mDiscreteOpCache.evict();
+ evictedEvents = mDiscreteOpCache.evictOldAppOpEvents();
// if nothing to evict, just write the whole cache to database.
if (evictedEvents.isEmpty()
&& mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) {
@@ -459,9 +455,10 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
}
/**
- * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}.
+ * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization} i.e.
+ * app op events older than one minute (default quantization) will be evicted.
*/
- private List<DiscreteOp> evict() {
+ private List<DiscreteOp> evictOldAppOpEvents() {
synchronized (this) {
List<DiscreteOp> evictedEvents = new ArrayList<>();
Set<DiscreteOp> snapshot = new ArraySet<>(mCache);
@@ -478,11 +475,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
}
/**
- * Remove all the entries from cache.
- *
- * @return return all removed entries.
+ * Evict all app op entries from cache, and return the list of removed ops.
*/
- public List<DiscreteOp> getAllEventsAndClear() {
+ public List<DiscreteOp> evictAllAppOpEvents() {
synchronized (this) {
List<DiscreteOp> cachedOps = new ArrayList<>(mCache.size());
if (mCache.isEmpty()) {
@@ -494,6 +489,25 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
}
}
+ /**
+ * Evict specified app ops from cache, and return the list of evicted ops.
+ */
+ public List<DiscreteOp> evictAppOpEvents(IntArray ops) {
+ synchronized (this) {
+ List<DiscreteOp> evictedOps = new ArrayList<>();
+ if (mCache.isEmpty()) {
+ return evictedOps;
+ }
+ for (DiscreteOp discreteOp: mCache) {
+ if (ops.contains(discreteOp.getOpCode())) {
+ evictedOps.add(discreteOp);
+ }
+ }
+ evictedOps.forEach(mCache::remove);
+ return evictedOps;
+ }
+ }
+
int size() {
return mCache.size();
}
@@ -654,7 +668,10 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
+ ", uidState=" + getUidStateName(mUidState)
+ ", chainId=" + mChainId
+ ", accessTime=" + mAccessTime
- + ", duration=" + mDuration + '}';
+ + ", mDiscretizedAccessTime=" + mDiscretizedAccessTime
+ + ", duration=" + mDuration
+ + ", mDiscretizedDuration=" + mDiscretizedDuration
+ + '}';
}
public int getUid() {
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
index 1523cca86607..909a04c44ae5 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
@@ -48,15 +48,13 @@ class DiscreteOpsTestingShim extends DiscreteOpsRegistry {
@Override
void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
@Nullable String attributionTag, int flags, int uidState, long accessTime,
- long accessDuration, int attributionFlags, int attributionChainId, int accessType) {
+ long accessDuration, int attributionFlags, int attributionChainId) {
long start = SystemClock.uptimeMillis();
mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags,
- uidState, accessTime, accessDuration, attributionFlags, attributionChainId,
- accessType);
+ uidState, accessTime, accessDuration, attributionFlags, attributionChainId);
long start2 = SystemClock.uptimeMillis();
mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags,
- uidState, accessTime, accessDuration, attributionFlags, attributionChainId,
- accessType);
+ uidState, accessTime, accessDuration, attributionFlags, attributionChainId);
long end = SystemClock.uptimeMillis();
long xmlTimeTaken = start2 - start;
long sqlTimeTaken = end - start2;
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
index a6e3fc7cc66a..20706b659ffb 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
@@ -45,7 +45,6 @@ import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -159,15 +158,7 @@ class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry {
void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
@Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
- @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @AccessType int accessType) {
- if (shouldLogAccess(op)) {
- FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
- uidState, flags, attributionFlags,
- getAttributionTag(attributionTag, packageName),
- attributionChainId);
- }
-
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
if (!isDiscreteOp(op, flags)) {
return;
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index d267e0d9e536..a8128dd11cfe 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -128,7 +128,7 @@ import java.util.concurrent.TimeUnit;
*/
// TODO (bug:122218838): Make sure we handle start of epoch time
// TODO (bug:122218838): Validate changed time is handled correctly
-final class HistoricalRegistry {
+final class HistoricalRegistry implements HistoricalRegistryInterface {
private static final boolean DEBUG = false;
private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE;
@@ -218,7 +218,8 @@ final class HistoricalRegistry {
mDiscreteRegistry = other.mDiscreteRegistry;
}
- void systemReady(@NonNull ContentResolver resolver) {
+ @Override
+ public void systemReady(@NonNull ContentResolver resolver) {
mDiscreteRegistry.systemReady();
final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
resolver.registerContentObserver(uri, false, new ContentObserver(
@@ -320,8 +321,10 @@ final class HistoricalRegistry {
+ "=" + setting + " resetting!");
}
- void dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage,
- @Nullable String filterAttributionTag, int filterOp,
+
+ @Override
+ public void dump(String prefix, PrintWriter pw, int filterUid,
+ @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp,
@HistoricalOpsRequestFilter int filter) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
@@ -366,7 +369,8 @@ final class HistoricalRegistry {
}
}
- void dumpDiscreteData(@NonNull PrintWriter pw, int uidFilter,
+ @Override
+ public void dumpDiscreteData(@NonNull PrintWriter pw, int uidFilter,
@Nullable String packageNameFilter, @Nullable String attributionTagFilter,
@HistoricalOpsRequestFilter int filter, int dumpOp,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
@@ -381,7 +385,8 @@ final class HistoricalRegistry {
}
}
- void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
+ @Override
+ public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String[] opNames,
@OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
@@ -414,11 +419,12 @@ final class HistoricalRegistry {
callback.sendResult(payload);
}
- void getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String[] opNames, @OpHistoryFlags int historyFlags,
- @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @Nullable String[] attributionExemptPkgs,
- @NonNull RemoteCallback callback) {
+ @Override
+ public void getHistoricalOps(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] opNames,
+ @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
+ long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
+ @Nullable String[] attributionExemptPkgs, @NonNull RemoteCallback callback) {
final long currentTimeMillis = System.currentTimeMillis();
if (endTimeMillis == Long.MAX_VALUE) {
endTimeMillis = currentTimeMillis;
@@ -493,11 +499,12 @@ final class HistoricalRegistry {
callback.sendResult(payload);
}
- void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
+ @Override
+ public void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long accessTime,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteOpsRegistry.AccessType int accessType, int accessCount) {
+ int accessCount) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -510,12 +517,13 @@ final class HistoricalRegistry {
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, accessTime, -1, attributionFlags,
- attributionChainId, accessType);
+ attributionChainId);
}
}
}
- void incrementOpRejected(int op, int uid, @NonNull String packageName,
+ @Override
+ public void incrementOpRejectedCount(int op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @UidState int uidState, @OpFlags int flags) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
@@ -530,11 +538,11 @@ final class HistoricalRegistry {
}
}
- void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
+ @Override
+ public void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long eventStartTime, long increment,
- @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteOpsRegistry.AccessType int accessType) {
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -546,12 +554,13 @@ final class HistoricalRegistry {
attributionTag, uidState, flags, increment);
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, eventStartTime, increment,
- attributionFlags, attributionChainId, accessType);
+ attributionFlags, attributionChainId);
}
}
}
- void setHistoryParameters(@HistoricalMode int mode,
+ @Override
+ public void setHistoryParameters(@HistoricalMode int mode,
long baseSnapshotInterval, long intervalCompressionMultiplier) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
@@ -586,7 +595,8 @@ final class HistoricalRegistry {
}
}
- void offsetHistory(long offsetMillis) {
+ @Override
+ public void offsetHistory(long offsetMillis) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
@@ -608,13 +618,11 @@ final class HistoricalRegistry {
mPersistence.persistHistoricalOpsDLocked(history);
}
}
- }
-
- void offsetDiscreteHistory(long offsetMillis) {
mDiscreteRegistry.offsetHistory(offsetMillis);
}
- void addHistoricalOps(HistoricalOps ops) {
+ @Override
+ public void addHistoricalOps(HistoricalOps ops) {
final List<HistoricalOps> pendingWrites;
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
@@ -635,7 +643,8 @@ final class HistoricalRegistry {
offsetHistory(offsetMillis);
}
- void resetHistoryParameters() {
+ @Override
+ public void resetHistoryParameters() {
if (!isPersistenceInitializedMLocked()) {
Slog.d(LOG_TAG, "Interaction before persistence initialized");
return;
@@ -645,7 +654,8 @@ final class HistoricalRegistry {
mDiscreteRegistry.setDebugMode(false);
}
- void clearHistory(int uid, String packageName) {
+ @Override
+ public void clearHistory(int uid, String packageName) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
@@ -669,11 +679,13 @@ final class HistoricalRegistry {
mDiscreteRegistry.clearHistory(uid, packageName);
}
- void writeAndClearDiscreteHistory() {
+ @Override
+ public void writeAndClearDiscreteHistory() {
mDiscreteRegistry.writeAndClearOldAccessHistory();
}
- void clearAllHistory() {
+ @Override
+ public void clearAllHistory() {
clearHistoricalRegistry();
mDiscreteRegistry.clearHistory();
}
@@ -742,7 +754,8 @@ final class HistoricalRegistry {
return mCurrentHistoricalOps;
}
- void shutdown() {
+ @Override
+ public void shutdown() {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
return;
@@ -753,7 +766,8 @@ final class HistoricalRegistry {
mDiscreteRegistry.shutdown();
}
- void persistPendingHistory() {
+ @Override
+ public void persistPendingHistory() {
final List<HistoricalOps> pendingWrites;
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistryInterface.java b/services/core/java/com/android/server/appop/HistoricalRegistryInterface.java
new file mode 100644
index 000000000000..b6210a727e6d
--- /dev/null
+++ b/services/core/java/com/android/server/appop/HistoricalRegistryInterface.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.os.RemoteCallback;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * A registry to record app operation access events, which are generated upon an application's
+ * access to private data or system resources. These events are stored in both aggregated
+ * and individual/discrete formats.
+ */
+public interface HistoricalRegistryInterface {
+ /**
+ * A callback to inform system components are ready.
+ */
+ void systemReady(@NonNull ContentResolver resolver);
+
+ /**
+ * Callback for system shutdown.
+ */
+ void shutdown();
+
+ /**
+ * Dumps aggregated/historical events to the console based on the filters.
+ */
+ void dump(String prefix, PrintWriter pw, int filterUid,
+ @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter);
+
+ /**
+ * Dumps discrete/individual events to the console based on filters.
+ */
+ void dumpDiscreteData(@NonNull PrintWriter pw, int uidFilter,
+ @Nullable String packageNameFilter, @Nullable String attributionTagFilter,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+ int nDiscreteOps);
+
+ /**
+ * Record duration for given op.
+ */
+ void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
+ @NonNull String deviceId, @Nullable String attributionTag,
+ @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, long eventStartTime, long increment,
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId);
+
+ /**
+ * Record access counts for given op.
+ */
+ void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
+ @NonNull String deviceId, @Nullable String attributionTag,
+ @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, long accessTime,
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+ int accessCount);
+
+ /**
+ * Record rejected counts for given op.
+ */
+ void incrementOpRejectedCount(int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags);
+
+ /**
+ * Read historical ops from both aggregated and discrete events based on input filter.
+ */
+ void getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @Nullable String[] opNames, @AppOpsManager.OpHistoryFlags int historyFlags,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter, long beginTimeMillis,
+ long endTimeMillis,
+ @AppOpsManager.OpFlags int flags, @Nullable String[] attributionExemptPkgs,
+ @NonNull RemoteCallback callback);
+
+ /**
+ * Remove app op events for a given UID and package.
+ */
+ void clearHistory(int uid, String packageName);
+
+ /**
+ * A periodic callback from {@link AppOpsService} to flush the in memory discrete
+ * app op events to disk/database.
+ */
+ void writeAndClearDiscreteHistory();
+
+ /**
+ * A callback flush the in memory app op events to disk/database.
+ */
+ void persistPendingHistory();
+
+ /**
+ * Set history parameters.
+ *
+ * @param mode - Whether historical registry is Active, Passive or Disabled.
+ * @param baseSnapshotInterval - Interval between 2 snapshots, default 15 minutes.
+ * @param intervalCompressionMultiplier - Interval compression multiplier, default is 10.
+ */
+ void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, long intervalCompressionMultiplier);
+
+ /**
+ * Reset history parameters to defaults.
+ */
+ void resetHistoryParameters();
+
+ /**
+ * Remove all app op accesses from both aggregated and individual event's storage.
+ */
+ void clearAllHistory();
+
+ /**
+ * Offsets the history by the given duration.
+ */
+ void offsetHistory(long offsetMillis);
+
+ /**
+ * Retrieve historical app op stats for a period form disk.
+ */
+ void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] opNames,
+ @AppOpsManager.OpHistoryFlags int historyFlags,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter,
+ long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flags,
+ String[] attributionExemptedPackages, @NonNull RemoteCallback callback);
+
+ /**
+ * Adds ops to the history directly. This could be useful for testing especially
+ * when the historical registry operates in passive mode.
+ */
+ void addHistoricalOps(AppOpsManager.HistoricalOps ops);
+}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistrySql.java b/services/core/java/com/android/server/appop/HistoricalRegistrySql.java
new file mode 100644
index 000000000000..cf5b941c7b33
--- /dev/null
+++ b/services/core/java/com/android/server/appop/HistoricalRegistrySql.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.RemoteCallback;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+// TODO add more documentation later
+
+/**
+ * This historical registry implementation store app events in sqlite. The data is stored in 2
+ * tables 1) discrete events 2) aggregated events.
+ */
+public class HistoricalRegistrySql implements HistoricalRegistryInterface {
+ // TODO impl will be added in a separate CL
+ HistoricalRegistrySql(Context context) {
+ }
+
+ HistoricalRegistrySql(HistoricalRegistrySql other) {
+ }
+
+ @Override
+ public void systemReady(@NonNull ContentResolver resolver) {
+
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+
+ @Override
+ public void dump(String prefix, PrintWriter pw, int filterUid,
+ @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp,
+ int filter) {
+
+ }
+
+ @Override
+ public void dumpDiscreteData(@NonNull PrintWriter pw, int uidFilter,
+ @Nullable String packageNameFilter, @Nullable String attributionTagFilter, int filter,
+ int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+ int nDiscreteOps) {
+
+ }
+
+ @Override
+ public void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
+ @NonNull String deviceId, @Nullable String attributionTag, int uidState, int flags,
+ long eventStartTime, long increment, int attributionFlags, int attributionChainId) {
+
+ }
+
+ @Override
+ public void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
+ @NonNull String deviceId, @Nullable String attributionTag, int uidState, int flags,
+ long accessTime, int attributionFlags, int attributionChainId, int accessCount) {
+
+ }
+
+ @Override
+ public void incrementOpRejectedCount(int op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int uidState, int flags) {
+
+ }
+
+ @Override
+ public void getHistoricalOps(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] opNames, int historyFlags,
+ int filter, long beginTimeMillis, long endTimeMillis, int flags,
+ @Nullable String[] attributionExemptPkgs, @NonNull RemoteCallback callback) {
+
+ }
+
+ @Override
+ public void clearHistory(int uid, String packageName) {
+
+ }
+
+ @Override
+ public void writeAndClearDiscreteHistory() {
+
+ }
+
+ @Override
+ public void persistPendingHistory() {
+
+ }
+
+ @Override
+ public void setHistoryParameters(int mode, long baseSnapshotInterval,
+ long intervalCompressionMultiplier) {
+
+ }
+
+ @Override
+ public void resetHistoryParameters() {
+
+ }
+
+ @Override
+ public void clearAllHistory() {
+
+ }
+
+ @Override
+ public void offsetHistory(long offsetMillis) {
+
+ }
+
+ @Override
+ public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] opNames, int historyFlags,
+ int filter, long beginTimeMillis, long endTimeMillis, int flags,
+ String[] attributionExemptedPackages, @NonNull RemoteCallback callback) {
+
+ }
+
+ @Override
+ public void addHistoricalOps(AppOpsManager.HistoricalOps ops) {
+
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ef80d59993e9..4b5f06b13885 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -16,6 +16,22 @@
package com.android.server.audio;
import static android.media.audio.Flags.scoManagedByAudio;
+import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET;
+import static android.media.AudioSystem.DEVICE_IN_BLE_HEADSET;
+import static android.media.AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+import static android.media.AudioSystem.DEVICE_IN_USB_HEADSET;
+import static android.media.AudioSystem.DEVICE_IN_WIRED_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET;
+import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_BUS;
+import static android.media.AudioSystem.DEVICE_OUT_EARPIECE;
+import static android.media.AudioSystem.DEVICE_OUT_SPEAKER;
+import static android.media.AudioSystem.DEVICE_OUT_USB_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_WIRED_HEADSET;
+import static android.media.AudioSystem.isBluetoothScoOutDevice;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
@@ -78,9 +94,11 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
@@ -514,7 +532,7 @@ public class AudioDeviceBroker {
@GuardedBy("mDeviceStateLock")
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
- if (crc.getUid() == mAudioModeOwner.mUid) {
+ if (crc.getUid() == mAudioModeOwner.mUid && !crc.isDisabled()) {
return crc;
}
}
@@ -574,36 +592,6 @@ public class AudioDeviceBroker {
return false;
}
- /*package */
- void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
- if (!isValidCommunicationDeviceType(
- AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) {
- return;
- }
- sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device);
- }
-
- @GuardedBy("mDeviceStateLock")
- void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString());
- }
- for (CommunicationRouteClient crc : mCommunicationRouteClients) {
- if (device.equals(crc.getDevice())) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: "
- + crc.toString());
- }
- // Cancelling the route for this client will remove it from the stack and update
- // the communication route.
- CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
- crc.getBinder(), crc.getAttributionSource(), device, false,
- BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
- crc.isPrivileged());
- postSetCommunicationDeviceForClient(deviceInfo);
- }
- }
- }
// check playback or record activity after 6 seconds for UIDs
private static final int CHECK_CLIENT_STATE_DELAY_MS = 6000;
@@ -1484,8 +1472,8 @@ public class AudioDeviceBroker {
mAudioService.postAccessoryPlugMediaUnmute(device);
}
- /*package*/ int getVssVolumeForDevice(int streamType, int device) {
- return mAudioService.getVssVolumeForDevice(streamType, device);
+ /*package*/ int getVolumeForDeviceIgnoreMute(int streamType, int device) {
+ return mAudioService.getVolumeForDeviceIgnoreMute(streamType, device);
}
/*package*/ int getMaxVssVolumeForStream(int streamType) {
@@ -1693,8 +1681,18 @@ public class AudioDeviceBroker {
boolean connect, @Nullable BluetoothDevice btDevice,
boolean deviceSwitch) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.handleDeviceConnection(
+ boolean status = mDeviceInventory.handleDeviceConnection(
attributes, connect, false /*for test*/, btDevice, deviceSwitch);
+ if (isValidCommunicationDeviceType(attributes.getType())
+ || mDuplexCommunicationDevices.containsValue(attributes.getInternalType())) {
+ checkCommunicationRouteClientsDevices();
+ if (connect || !deviceSwitch) {
+ onUpdateCommunicationRouteClient(
+ bluetoothScoRequestOwnerAttributionSource(),
+ "handleDeviceConnection");
+ }
+ }
+ return status;
}
}
@@ -1953,15 +1951,17 @@ public class AudioDeviceBroker {
|| btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
- if ((btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.HEARING_AID
|| (mScoManagedByAudio
- && btInfo.mProfile == BluetoothProfile.HEADSET))
- && (btInfo.mState == BluetoothProfile.STATE_CONNECTED
- || !btInfo.mIsDeviceSwitch)) {
- onUpdateCommunicationRouteClient(
+ && btInfo.mProfile == BluetoothProfile.HEADSET)) {
+ checkCommunicationRouteClientsDevices();
+ if (btInfo.mState == BluetoothProfile.STATE_CONNECTED
+ || !btInfo.mIsDeviceSwitch) {
+ onUpdateCommunicationRouteClient(
bluetoothScoRequestOwnerAttributionSource(),
"setBluetoothActiveDevice");
+ }
}
}
}
@@ -2123,13 +2123,6 @@ public class AudioDeviceBroker {
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
} break;
- case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: {
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj);
- }
- }
- } break;
case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
onPersistAudioDeviceSettings();
break;
@@ -2227,7 +2220,6 @@ public class AudioDeviceBroker {
private static final int MSG_IIL_BTLEAUDIO_TIMEOUT = 49;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
- private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;
@@ -2431,18 +2423,21 @@ public class AudioDeviceBroker {
private final IBinder mCb;
@NonNull private final AttributionSource mAttributionSource;
private final boolean mIsPrivileged;
- private AudioDeviceAttributes mDevice;
+ @NonNull private AudioDeviceAttributes mDevice;
private boolean mPlaybackActive;
private boolean mRecordingActive;
+ private boolean mDisabled;
+
CommunicationRouteClient(IBinder cb, @NonNull AttributionSource attributionSource,
- AudioDeviceAttributes device, boolean isPrivileged) {
+ @NonNull AudioDeviceAttributes device, boolean isPrivileged) {
mCb = cb;
mAttributionSource = attributionSource;
mDevice = device;
mIsPrivileged = isPrivileged;
mPlaybackActive = mAudioService.isPlaybackActiveForUid(attributionSource.getUid());
mRecordingActive = mAudioService.isRecordingActiveForUid(attributionSource.getUid());
+ mDisabled = false;
}
public boolean registerDeathRecipient() {
@@ -2485,7 +2480,10 @@ public class AudioDeviceBroker {
return mIsPrivileged;
}
- AudioDeviceAttributes getDevice() {
+ void setDevice(@NonNull AudioDeviceAttributes device) {
+ mDevice = device;
+ }
+ @NonNull AudioDeviceAttributes getDevice() {
return mDevice;
}
@@ -2498,7 +2496,14 @@ public class AudioDeviceBroker {
}
public boolean isActive() {
- return mIsPrivileged || mRecordingActive || mPlaybackActive;
+ return !mDisabled && (mIsPrivileged || mRecordingActive || mPlaybackActive);
+ }
+
+ public void setDisabled(boolean disabled) {
+ mDisabled = disabled;
+ }
+ public boolean isDisabled() {
+ return mDisabled;
}
@Override
@@ -2507,7 +2512,8 @@ public class AudioDeviceBroker {
+ " mDevice: " + mDevice.toString()
+ " mIsPrivileged: " + mIsPrivileged
+ " mPlaybackActive: " + mPlaybackActive
- + " mRecordingActive: " + mRecordingActive + "]";
+ + " mRecordingActive: " + mRecordingActive
+ + " mDisabled: " + mDisabled + "]";
}
}
@@ -2593,6 +2599,76 @@ public class AudioDeviceBroker {
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
}
+ // Pairs of input and output devices for duplex communication devices (headsets)
+ private final HashMap<Integer, Integer> mDuplexCommunicationDevices = new HashMap<>(
+ Map.of(DEVICE_OUT_BLE_HEADSET, DEVICE_IN_BLE_HEADSET,
+ DEVICE_OUT_WIRED_HEADSET, DEVICE_IN_WIRED_HEADSET,
+ DEVICE_OUT_USB_HEADSET, DEVICE_IN_USB_HEADSET,
+ DEVICE_OUT_BLUETOOTH_SCO, DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ DEVICE_OUT_BLUETOOTH_SCO_HEADSET, DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ DEVICE_OUT_BLUETOOTH_SCO_CARKIT, DEVICE_IN_BLUETOOTH_SCO_HEADSET
+ ));
+ /**
+ * Scan communication route clients and disable them if their selected device is not connected
+ * or re-enable them if a device of the same type as their connected device is connected
+ */
+ // @GuardedBy("mSetModeLock")
+ @GuardedBy("mDeviceStateLock")
+ private void checkCommunicationRouteClientsDevices() {
+ for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+ int deviceType = crc.getDevice().getInternalType();
+ // Skip non detachable devices
+ if (deviceType == DEVICE_OUT_EARPIECE || deviceType == DEVICE_OUT_SPEAKER
+ || deviceType == DEVICE_OUT_BUS) {
+ continue;
+ }
+
+ // outDeviceSet is the expected connected output device types for the requested device
+ Set<Integer> outDeviceSet = null;
+ // inDeviceSet is the expected input device for outDeviceSet. Null for non
+ // duplex devices
+ Set<Integer> inDeviceSet = null;
+ // Special case for SCO because several device types are equivalent
+ if (isBluetoothScoOutDevice(deviceType)) {
+ outDeviceSet = DEVICE_OUT_ALL_SCO_SET;
+ inDeviceSet = DEVICE_IN_ALL_SCO_SET;
+ } else {
+ outDeviceSet = new HashSet<>();
+ outDeviceSet.add(deviceType);
+ if (mDuplexCommunicationDevices.containsKey(deviceType)) {
+ inDeviceSet = new HashSet<>();
+ inDeviceSet.add(mDuplexCommunicationDevices.get(deviceType));
+ }
+ }
+
+ AudioDeviceAttributes outAda =
+ mDeviceInventory.getFirstConnectedDeviceAttributesOfTypes(outDeviceSet);
+ AudioDeviceAttributes inAda = (inDeviceSet == null) ? null
+ : mDeviceInventory.getFirstConnectedDeviceAttributesOfTypes(inDeviceSet);
+
+ // A device is fully connected if the output device is connect and if not duplex
+ // or an input device with the same address is connected
+ boolean fullyConnected = outAda != null && (inDeviceSet == null
+ || (inAda != null && inAda.getAddress().equals(outAda.getAddress())));
+
+ if (fullyConnected) {
+ crc.setDevice(outAda);
+ if (crc.isDisabled()) {
+ crc.setDisabled(false);
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG,
+ "checkCommunicationRouteClientsDevices, enabling client: " + crc);
+ }
+ }
+ } else if (!crc.isDisabled()) {
+ crc.setDisabled(true);
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "checkCommunicationRouteClientsDevices, disabling client: " + crc);
+ }
+ }
+ }
+ }
+
/**
* Select new communication device from communication route client at the top of the stack
* and restore communication route including restarting SCO audio if needed.
@@ -2601,6 +2677,7 @@ public class AudioDeviceBroker {
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(
@Nullable AttributionSource previousBtScoRequesterAS, String eventSource) {
+
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ae91934e7498..2e6d98485e85 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1867,7 +1867,6 @@ public class AudioDeviceInventory {
deviceSwitch);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
- mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
status = true;
}
if (status) {
@@ -2413,7 +2412,7 @@ public class AudioDeviceInventory {
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable, deviceSwitch" + deviceSwitch))
+ + " made unavailable, deviceSwitch: " + deviceSwitch))
.printSlog(EventLogger.Event.ALOGI, TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -2423,7 +2422,6 @@ public class AudioDeviceInventory {
mmi.record();
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l();
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -2479,13 +2477,12 @@ public class AudioDeviceInventory {
// always remove regardless of the result
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
private void makeHearingAidDeviceAvailable(
String address, String name, int streamType, String eventSource) {
- final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
+ final int hearingAidVolIndex = mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType,
DEVICE_OUT_HEARING_AID);
mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
@@ -2541,7 +2538,6 @@ public class AudioDeviceInventory {
.set(MediaMetrics.Property.DEVICE,
AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID))
.record();
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -2572,6 +2568,15 @@ public class AudioDeviceInventory {
}
/**
+ * Returns a DeviceInfo for the first connected device matching one of the supplied types
+ */
+ AudioDeviceAttributes getFirstConnectedDeviceAttributesOfTypes(Set<Integer> internalTypes) {
+ DeviceInfo di = getFirstConnectedDeviceOfTypes(internalTypes);
+ return di == null ? null : new AudioDeviceAttributes(
+ di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+ }
+
+ /**
* Returns a list of connected devices matching one of the supplied types
*/
private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
@@ -2667,7 +2672,7 @@ public class AudioDeviceInventory {
}
final int leAudioVolIndex = (volumeIndex == -1)
- ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
+ ? mDeviceBroker.getVolumeForDeviceIgnoreMute(streamType, device)
: volumeIndex;
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
@@ -2689,13 +2694,16 @@ public class AudioDeviceInventory {
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM failed to make unavailable LE Audio device addr=" + address
- + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
+ "APM failed to make unavailable LE Audio "
+ + (AudioSystem.isInputDevice(device) ? "source" : "sink")
+ + " device addr=" + address
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
// not taking further action: proceeding as if disconnection from APM worked
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable, deviceSwitch" + deviceSwitch)
+ "LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
+ + "device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " made unavailable, deviceSwitch: " + deviceSwitch)
.printSlog(EventLogger.Event.ALOGI, TAG));
}
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
@@ -2704,9 +2712,6 @@ public class AudioDeviceInventory {
setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l();
- if (ada != null) {
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
- }
}
@GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f1228f44b0d..d917bffa06e9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -529,7 +529,7 @@ public class AudioService extends IAudioService.Stub
*/
private InputDeviceVolumeHelper mInputDeviceVolumeHelper;
- /*package*/ int getVssVolumeForDevice(int stream, int device) {
+ /*package*/ int getVolumeForDeviceIgnoreMute(int stream, int device) {
final VolumeStreamState streamState = mStreamStates.get(stream);
return streamState != null ? streamState.getIndex(device) : -1;
}
@@ -4997,6 +4997,8 @@ public class AudioService extends IAudioService.Stub
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
pw.println("\tcom.android.media.audio.setStreamVolumeOrder - EOL");
+ pw.println("\tandroid.media.audio.ringtoneUserUriCheck:"
+ + android.media.audio.Flags.ringtoneUserUriCheck());
pw.println("\tandroid.media.audio.roForegroundAudioControl:"
+ roForegroundAudioControl());
pw.println("\tandroid.media.audio.scoManagedByAudio:"
@@ -5098,7 +5100,7 @@ public class AudioService extends IAudioService.Stub
}
final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue();
- final int index = getStreamVolume(streamType, device);
+ final int index = (getVolumeForDeviceIgnoreMute(streamType, device) + 5) / 10;
if (DEBUG_VOL) {
Slog.i(TAG, "onUpdateContextualVolumes streamType: " + streamType
@@ -7806,7 +7808,8 @@ public class AudioService extends IAudioService.Stub
return AudioSystem.STREAM_RING;
}
default:
- if (isInCommunication()) {
+ if (isInCommunication()
+ || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
if (!replaceStreamBtSco()
&& mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
@@ -15106,6 +15109,61 @@ public class AudioService extends IAudioService.Stub
/**
* @hide
+ * Returns the current audio output device delay value of key in milliseconds.
+ *
+ * In aidl implementation, the param of getParameters is "key" and reply delay of all supported
+ * devices which be composited as "key=device,address,delay|device,address,delay|...".
+ * e.g.
+ * param: additional_output_device_delay=
+ * reply: additional_output_device_delay=2,,0|4,,0|8,,0|128,,0|256,,0|512,,0|1024,,0|8192,,0|
+ * 16384,,0|262144,,0|262145,,0|524288,,0|67108864,,0|134217728,,0|536870912,,0|536870913,,0|
+ * 536870914,,0
+ *
+ * In hidl implementation, the param of getParameters is "key=device,address" and reply a
+ * specific device delay which be composited as "key=device,address,delay".
+ * e.g.
+ * param: additional_output_device_delay=2,
+ * reply: additional_output_device_delay=2,,0
+ *
+ * @param key
+ * @param deviceType
+ * @param address
+ * @return the delay value of key. This is a non-negative number.
+ * {@code 0} is returned if unsupported.
+ */
+ private long getDelayByKeyDevice(@NonNull String key, @NonNull AudioDeviceAttributes device) {
+ long delayMillis = 0;
+
+ try {
+ if (AudioHalVersionInfo.AUDIO_HAL_TYPE_AIDL == getHalVersion().getHalType()) {
+ final String reply = AudioSystem.getParameters(key);
+ final String keyDeviceAddressPrefix =
+ Integer.toUnsignedString(device.getInternalType()) + "," + device.getAddress() +
+ ",";
+ int start = reply.indexOf(keyDeviceAddressPrefix);
+ int end = -1;
+ if (start != -1) {
+ start += keyDeviceAddressPrefix.length();
+ end = reply.indexOf("|", start);
+ delayMillis = Long.parseLong(
+ end == -1 ? reply.substring(start) : reply.substring(start, end));
+ }
+ } else {
+ final String reply = AudioSystem.getParameters(
+ key + "=" + device.getInternalType() + "," + device.getAddress());
+ if (reply.contains(key)) {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ }
+ }
+ } catch (NullPointerException e) {
+ Log.w(TAG, "NullPointerException when getting delay for device " + device, e);
+ }
+
+ return delayMillis;
+ }
+
+ /**
+ * @hide
* Returns the current additional audio output device delay in milliseconds.
*
* @param deviceType
@@ -15121,17 +15179,8 @@ public class AudioService extends IAudioService.Stub
device = retrieveBluetoothAddress(device);
final String key = "additional_output_device_delay";
- final String reply = AudioSystem.getParameters(
- key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis = 0;
- if (reply.contains(key)) {
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
- }
- }
- return delayMillis;
+
+ return getDelayByKeyDevice(key, device);
}
/**
@@ -15153,17 +15202,8 @@ public class AudioService extends IAudioService.Stub
device = retrieveBluetoothAddress(device);
final String key = "max_additional_output_device_delay";
- final String reply = AudioSystem.getParameters(
- key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis = 0;
- if (reply.contains(key)) {
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
- }
- }
- return delayMillis;
+
+ return getDelayByKeyDevice(key, device);
}
@android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING)
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 643f3308d8f5..67afff79dffd 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -724,7 +724,7 @@ public class SoundDoseHelper {
int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
if (safeDevicesContains(device) && isStreamActive) {
scheduleMusicActiveCheck();
- int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
+ int index = mAudioService.getVolumeForDeviceIgnoreMute(AudioSystem.STREAM_MUSIC,
device);
if (index > safeMediaVolumeIndex(device)) {
// Approximate cumulative active music time
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
index 8637d2dfe565..47b1c5449dc2 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -103,12 +103,6 @@ public final class AppCompatOverridesService {
}
}
- @Override
- public void finalize() {
- unregisterDeviceConfigListeners();
- unregisterPackageReceiver();
- }
-
@VisibleForTesting
void registerDeviceConfigListeners() {
for (DeviceConfigListener listener : mDeviceConfigListeners) {
@@ -116,21 +110,11 @@ public final class AppCompatOverridesService {
}
}
- private void unregisterDeviceConfigListeners() {
- for (DeviceConfigListener listener : mDeviceConfigListeners) {
- listener.unregister();
- }
- }
-
@VisibleForTesting
void registerPackageReceiver() {
mPackageReceiver.register();
}
- private void unregisterPackageReceiver() {
- mPackageReceiver.unregister();
- }
-
/**
* Same as {@link #applyOverrides(Properties, Set, Map)} except all properties of the given
* {@code namespace} are fetched via {@link DeviceConfig#getProperties}.
@@ -374,10 +358,6 @@ public final class AppCompatOverridesService {
this);
}
- private void unregister() {
- DeviceConfig.removeOnPropertiesChangedListener(this);
- }
-
@Override
public void onPropertiesChanged(Properties properties) {
boolean removeOverridesFlagChanged = properties.getKeyset().contains(
@@ -426,10 +406,6 @@ public final class AppCompatOverridesService {
null, /* scheduler= */ null);
}
- private void unregister() {
- mContext.unregisterReceiver(this);
- }
-
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
Uri data = intent.getData();
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 969a684f6ef5..2d387ea38df5 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -261,6 +261,7 @@ public class SyncManager {
private final SyncLogger mLogger;
private final AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+ private final PackageMonitorImpl mPackageMonitor;
private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) {
for (int i = 0, size = pendingJobs.size(); i < size; i++) {
@@ -725,8 +726,8 @@ public class SyncManager {
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
- final PackageMonitor packageMonitor = new PackageMonitorImpl();
- packageMonitor.register(mContext, null /* thread */, UserHandle.ALL,
+ mPackageMonitor = new PackageMonitorImpl();
+ mPackageMonitor.register(mContext, null /* thread */, UserHandle.ALL,
false /* externalStorage */);
intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 69bc66fea56c..155f82a421ae 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -19,6 +19,7 @@ package com.android.server.display;
import android.content.Context;
import android.os.Handler;
import android.view.Display;
+import android.view.SurfaceControl;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -138,6 +139,21 @@ abstract class DisplayAdapter {
vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes);
}
+ static int getPowerModeForState(int state) {
+ switch (state) {
+ case Display.STATE_OFF:
+ return SurfaceControl.POWER_MODE_OFF;
+ case Display.STATE_DOZE:
+ return SurfaceControl.POWER_MODE_DOZE;
+ case Display.STATE_DOZE_SUSPEND:
+ return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
+ case Display.STATE_ON_SUSPEND:
+ return SurfaceControl.POWER_MODE_ON_SUSPEND;
+ default:
+ return SurfaceControl.POWER_MODE_NORMAL;
+ }
+ }
+
public interface Listener {
void onDisplayDeviceEvent(DisplayDevice device, int event);
void onTraversalRequested();
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index ddea285d3564..1d70953de6c0 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -29,7 +29,7 @@ import java.util.Objects;
*/
public class DisplayControl {
private static native IBinder nativeCreateVirtualDisplay(String name, boolean secure,
- String uniqueId, float requestedRefreshRate);
+ boolean optimizeForPower, String uniqueId, float requestedRefreshRate);
private static native void nativeDestroyVirtualDisplay(IBinder displayToken);
private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
private static native long[] nativeGetPhysicalDisplayIds();
@@ -49,7 +49,7 @@ public class DisplayControl {
*/
public static IBinder createVirtualDisplay(String name, boolean secure) {
Objects.requireNonNull(name, "name must not be null");
- return nativeCreateVirtualDisplay(name, secure, "", 0.0f);
+ return nativeCreateVirtualDisplay(name, secure, true, "", 0.0f);
}
/**
@@ -57,6 +57,10 @@ public class DisplayControl {
*
* @param name The name of the virtual display.
* @param secure Whether this display is secure.
+ * @param optimizeForPower Whether SurfaceFlinger should optimize for power (instead of
+ * performance). Such displays will depend on another display for it to
+ * be shown and rendered, and that display will optimize for
+ * performance when it is on.
* @param uniqueId The unique ID for the display.
* @param requestedRefreshRate The requested refresh rate in frames per second.
* For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
@@ -66,10 +70,11 @@ public class DisplayControl {
* @return The token reference for the display in SurfaceFlinger.
*/
public static IBinder createVirtualDisplay(String name, boolean secure,
- String uniqueId, float requestedRefreshRate) {
+ boolean optimizeForPower, String uniqueId, float requestedRefreshRate) {
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(uniqueId, "uniqueId must not be null");
- return nativeCreateVirtualDisplay(name, secure, uniqueId, requestedRefreshRate);
+ return nativeCreateVirtualDisplay(name, secure, optimizeForPower, uniqueId,
+ requestedRefreshRate);
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 3aaf4f6fe85a..7450dffc05ab 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -122,8 +122,8 @@ final class DisplayDeviceInfo {
public static final int FLAG_MASK_DISPLAY_CUTOUT = 1 << 11;
/**
- * Flag: This flag identifies secondary displays that should show system decorations, such as
- * navigation bar, home activity or wallpaper.
+ * Flag: This flag identifies secondary displays that should always show system decorations,
+ * such as navigation bar, home activity or wallpaper.
* <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
* @hide
*/
@@ -191,6 +191,19 @@ final class DisplayDeviceInfo {
public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 19;
/**
+ * Flag: Indicates that the display is allowed to switch the content mode between
+ * projected/extended and mirroring. This allows the display to dynamically add or remove the
+ * home and system decorations.
+ *
+ * Note that this flag should not be enabled with any of {@link #FLAG_PRIVATE},
+ * {@link #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}, or {@link #FLAG_OWN_CONTENT_ONLY} at the
+ * same time; otherwise it will be ignored.
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOWS_CONTENT_MODE_SWITCH = 1 << 20;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index f73b66c78fce..ce8d8a6db9d3 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import android.util.IndentingPrintWriter;
+
import java.util.ArrayList;
import java.util.List;
@@ -97,4 +99,14 @@ public class DisplayGroup {
}
return displayIds;
}
+
+ /** Dumps information about the DisplayGroup. */
+ void dumpLocked(IndentingPrintWriter ipw) {
+ final int numDisplays = mDisplays.size();
+ for (int i = 0; i < numDisplays; i++) {
+ LogicalDisplay logicalDisplay = mDisplays.get(i);
+ ipw.println("Display " + logicalDisplay.getDisplayIdLocked() + " "
+ + logicalDisplay.getPrimaryDisplayDeviceLocked());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 258c95582e3a..a28069bbf050 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -674,9 +674,9 @@ public final class DisplayManagerService extends SystemService {
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
-
+ // TODO(b/400384229): stats service needs to react to mirror-extended switch
mExternalDisplayStatsService = new ExternalDisplayStatsService(mContext, mHandler,
- this::isExtendedDisplayEnabled);
+ this::isExtendedDisplayAllowed);
mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
mExternalDisplayStatsService);
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
@@ -690,7 +690,7 @@ public final class DisplayManagerService extends SystemService {
deliverTopologyUpdate(update.first);
};
mDisplayTopologyCoordinator = new DisplayTopologyCoordinator(
- this::isExtendedDisplayEnabled, topologyChangedCallback,
+ this::isExtendedDisplayAllowed, topologyChangedCallback,
new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged);
} else {
mDisplayTopologyCoordinator = null;
@@ -2411,7 +2411,10 @@ public final class DisplayManagerService extends SystemService {
updateLogicalDisplayState(display);
}
- private boolean isExtendedDisplayEnabled() {
+ private boolean isExtendedDisplayAllowed() {
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ return true;
+ }
try {
return 0 != Settings.Global.getInt(
mContext.getContentResolver(),
@@ -2442,7 +2445,10 @@ public final class DisplayManagerService extends SystemService {
applyDisplayChangedLocked(display);
}
- if (mDisplayTopologyCoordinator != null) {
+ // The default display should always be added to the topology. Other displays will be added
+ // upon calling onDisplayBelongToTopologyChanged().
+ if (mDisplayTopologyCoordinator != null
+ && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) {
mDisplayTopologyCoordinator.onDisplayAdded(display.getDisplayInfoLocked());
}
}
@@ -2618,8 +2624,7 @@ public final class DisplayManagerService extends SystemService {
// Blank or unblank the display immediately to match the state requested
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0
- || android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display == null) {
return null;
@@ -5580,9 +5585,7 @@ public final class DisplayManagerService extends SystemService {
final DisplayDevice displayDevice = mLogicalDisplayMapper.getDisplayLocked(
id).getPrimaryDisplayDeviceLocked();
final int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
- if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0
- || android.companion.virtualdevice.flags.Flags
- .correctVirtualDisplayPowerState()) {
+ if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(id);
if (displayPowerController != null) {
@@ -6040,6 +6043,24 @@ public final class DisplayManagerService extends SystemService {
}
@Override
+ public void onDisplayBelongToTopologyChanged(int displayId, boolean inTopology) {
+ if (mDisplayTopologyCoordinator == null) {
+ return;
+ }
+ if (inTopology) {
+ var info = getDisplayInfo(displayId);
+ if (info == null) {
+ Slog.w(TAG, "onDisplayBelongToTopologyChanged: cancelled displayId="
+ + displayId + " info=null");
+ return;
+ }
+ mDisplayTopologyCoordinator.onDisplayAdded(info);
+ } else {
+ mDisplayTopologyCoordinator.onDisplayRemoved(displayId);
+ }
+ }
+
+ @Override
public void reloadTopologies(final int userId) {
// Reload topologies only if the userId matches the current user id.
if (userId == mCurrentUserId) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 2618cf40d113..b4df1f76dccb 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -69,9 +69,9 @@ class DisplayTopologyCoordinator {
private final SparseArray<String> mDisplayIdToUniqueIdMapping = new SparseArray<>();
/**
- * Check if extended displays are enabled. If not, a topology is not needed.
+ * Check if extended displays are allowed. If not, a topology is not needed.
*/
- private final BooleanSupplier mIsExtendedDisplayEnabled;
+ private final BooleanSupplier mIsExtendedDisplayAllowed;
/**
* Callback used to send topology updates.
@@ -83,21 +83,21 @@ class DisplayTopologyCoordinator {
private final DisplayManagerService.SyncRoot mSyncRoot;
private final Runnable mTopologySavedCallback;
- DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
+ DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayAllowed,
Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback,
Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
Runnable topologySavedCallback) {
- this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
+ this(new Injector(), isExtendedDisplayAllowed, onTopologyChangedCallback,
topologyChangeExecutor, syncRoot, topologySavedCallback);
}
@VisibleForTesting
- DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
+ DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayAllowed,
Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback,
Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot,
Runnable topologySavedCallback) {
mTopology = injector.getTopology();
- mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
+ mIsExtendedDisplayAllowed = isExtendedDisplayAllowed;
mOnTopologyChangedCallback = onTopologyChangedCallback;
mTopologyChangeExecutor = topologyChangeExecutor;
mSyncRoot = syncRoot;
@@ -262,14 +262,9 @@ class DisplayTopologyCoordinator {
return false;
}
if ((info.type == Display.TYPE_EXTERNAL || info.type == Display.TYPE_OVERLAY)
- && !mIsExtendedDisplayEnabled.getAsBoolean()) {
+ && !mIsExtendedDisplayAllowed.getAsBoolean()) {
Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because "
- + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayEnabled");
- return false;
- }
- if (info.displayGroupId != Display.DEFAULT_DISPLAY_GROUP) {
- Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because "
- + "it is not in the default display group");
+ + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayAllowed");
return false;
}
return true;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 551202c20cbb..2cad7ed2e9e9 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -208,21 +208,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
- static int getPowerModeForState(int state) {
- switch (state) {
- case Display.STATE_OFF:
- return SurfaceControl.POWER_MODE_OFF;
- case Display.STATE_DOZE:
- return SurfaceControl.POWER_MODE_DOZE;
- case Display.STATE_DOZE_SUSPEND:
- return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
- case Display.STATE_ON_SUSPEND:
- return SurfaceControl.POWER_MODE_ON_SUSPEND;
- default:
- return SurfaceControl.POWER_MODE_NORMAL;
- }
- }
-
private final class LocalDisplayDevice extends DisplayDevice {
private final long mPhysicalDisplayId;
private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
@@ -781,7 +766,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
}
} else {
- if (!res.getBoolean(R.bool.config_localDisplaysMirrorContent)) {
+ if (shouldOwnContentOnly()) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
}
@@ -795,6 +780,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
+ if (getFeatureFlags().isDisplayContentModeManagementEnabled()) {
+ // Public display with FLAG_OWN_CONTENT_ONLY disabled is allowed to switch the
+ // content mode.
+ if (mIsFirstDisplay
+ || (!isDisplayPrivate(physicalAddress) && !shouldOwnContentOnly())) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
+ }
+
if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
}
@@ -837,6 +831,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
R.string.display_manager_hdmi_display_name);
}
}
+
mInfo.frameRateOverrides = mFrameRateOverrides;
// The display is trusted since it is created by system.
@@ -1482,6 +1477,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return false;
}
+ private boolean shouldOwnContentOnly() {
+ final Resources res = getOverlayContext().getResources();
+ return !res.getBoolean(R.bool.config_localDisplaysMirrorContent);
+ }
+
private boolean isDisplayStealTopFocusDisabled(DisplayAddress.Physical physicalAddress) {
if (physicalAddress == null) {
return false;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b2b9ef17ec8d..0e6870f7ed7d 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -489,6 +489,11 @@ final class LogicalDisplay {
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_STEAL_TOP_FOCUS_DISABLED;
}
+ // Rear display should not be allowed to use the content mode switch.
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0
+ && mDevicePosition != Layout.Display.POSITION_REAR) {
+ mBaseDisplayInfo.flags |= Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
Rect maskingInsets = getMaskingInsets(deviceInfo);
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
@@ -1155,6 +1160,7 @@ final class LogicalDisplay {
pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
+ pw.println("mDisplayGroupId=" + mDisplayGroupId);
pw.println("mDisplayGroupName=" + mDisplayGroupName);
pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
pw.println("mLeadDisplayId=" + mLeadDisplayId);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index f4daf8761e9b..4a4c616b34e3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -478,6 +478,21 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.decreaseIndent();
ipw.println();
}
+
+ final int displayGroupCount = mDisplayGroups.size();
+ ipw.println();
+ ipw.println("Display Groups: size=" + displayGroupCount);
+ for (int i = 0; i < displayGroupCount; i++) {
+ int groupId = mDisplayGroups.keyAt(i);
+ DisplayGroup displayGroup = mDisplayGroups.valueAt(i);
+ ipw.println("Group " + groupId + ":");
+ ipw.increaseIndent();
+ displayGroup.dumpLocked(ipw);
+ ipw.decreaseIndent();
+ ipw.println();
+ }
+
+
mDeviceStateToLayoutMap.dumpLocked(ipw);
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b5a9b19bc5c5..60b7fca99e7b 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -76,6 +76,7 @@ import java.util.regex.Pattern;
* <li><code>secure</code>: creates a secure display</li>
* <li><code>own_content_only</code>: only shows this display's own content</li>
* <li><code>should_show_system_decorations</code>: supports system decorations</li>
+ * <li><code>fixed_content_mode</code>: not allowed to switch content mode</li>
* <li><code>gravity_top_left</code>: display the overlay at the top left of the screen</li>
* <li><code>gravity_top_right</code>: display the overlay at the top right of the screen</li>
* <li><code>gravity_bottom_right</code>: display the overlay at the bottom right of the screen</li>
@@ -117,6 +118,18 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
"should_show_system_decorations";
+ /**
+ * When this flag is set, the overlay display is not allowed to switch content mode.
+ * Note that it is the opposite of {@link DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH},
+ * because we want overlay displays (such as those used for connected display simulation in
+ * development) to have {@link DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH} enabled by
+ * default without explicitly specifying it.
+ *
+ * @see DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH
+ */
+ private static final String OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE =
+ "fixed_content_mode";
+
// Gravity flags to decide where the overlay should be shown.
private static final String GRAVITY_TOP_LEFT = "gravity_top_left";
private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right";
@@ -384,6 +397,17 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
if (mFlags.mShouldShowSystemDecorations) {
mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
}
+ if (getFeatureFlags().isDisplayContentModeManagementEnabled()) {
+ if (!mFlags.mFixedContentMode
+ && !mFlags.mOwnContentOnly
+ && !mFlags.mShouldShowSystemDecorations) {
+ // For overlay displays, if FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS and
+ // FLAG_OWN_CONTENT_ONLY are both disabled,
+ // then FLAG_ALLOWS_CONTENT_MODE_SWITCH should be enabled by default,
+ // unless OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE is set.
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
+ }
mInfo.type = Display.TYPE_OVERLAY;
mInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
mInfo.state = mState;
@@ -628,16 +652,21 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
/** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */
final boolean mShouldShowSystemDecorations;
+ /** See {@link #OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE}. */
+ final boolean mFixedContentMode;
+
final int mGravity;
OverlayFlags(
boolean secure,
boolean ownContentOnly,
boolean shouldShowSystemDecorations,
+ boolean fixedContentMode,
int gravity) {
mSecure = secure;
mOwnContentOnly = ownContentOnly;
mShouldShowSystemDecorations = shouldShowSystemDecorations;
+ mFixedContentMode = fixedContentMode;
mGravity = gravity;
}
@@ -647,12 +676,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
false /* secure */,
false /* ownContentOnly */,
false /* shouldShowSystemDecorations */,
+ false /* fixedContentMode */,
Gravity.NO_GRAVITY);
}
boolean secure = false;
boolean ownContentOnly = false;
boolean shouldShowSystemDecorations = false;
+ boolean fixedContentMode = false;
int gravity = Gravity.NO_GRAVITY;
for (String flag: flagString.split(FLAG_SPLITTER)) {
if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) {
@@ -661,11 +692,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
ownContentOnly = true;
} else if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
shouldShowSystemDecorations = true;
+ } else if (OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE.equals(flag)) {
+ fixedContentMode = true;
} else {
gravity = parseOverlayGravity(flag);
}
}
- return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, gravity);
+ return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations,
+ fixedContentMode, gravity);
}
@Override
@@ -674,6 +708,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
.append("secure=").append(mSecure)
.append(", ownContentOnly=").append(mOwnContentOnly)
.append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations)
+ .append(", fixedContentMode=").append(mFixedContentMode)
.append(", gravity").append(Gravity.toString(mGravity))
.append("}")
.toString();
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index e7939bb50ece..c2eac8605851 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -103,16 +103,21 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) {
this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
@Override
- public IBinder createDisplay(String name, boolean secure, String uniqueId,
- float requestedRefreshRate) {
- return DisplayControl.createVirtualDisplay(name, secure, uniqueId,
- requestedRefreshRate);
+ public IBinder createDisplay(String name, boolean secure, boolean optimizeForPower,
+ String uniqueId, float requestedRefreshRate) {
+ return DisplayControl.createVirtualDisplay(name, secure, optimizeForPower, uniqueId,
+ requestedRefreshRate);
}
@Override
public void destroyDisplay(IBinder displayToken) {
DisplayControl.destroyVirtualDisplay(displayToken);
}
+
+ @Override
+ public void setDisplayPowerMode(IBinder displayToken, int mode) {
+ SurfaceControl.setDisplayPowerMode(displayToken, mode);
+ }
}, featureFlags);
}
@@ -177,9 +182,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
String name = virtualDisplayConfig.getName();
boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
+ boolean neverBlank = isNeverBlank(flags);
- IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId,
- virtualDisplayConfig.getRequestedRefreshRate());
+ // Never-blank displays are considered to be dependent on another display to be rendered.
+ // As a result, such displays should optimize for power instead of performance when it is
+ // powered on.
+ IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, neverBlank,
+ uniqueId, virtualDisplayConfig.getRequestedRefreshRate());
MediaProjectionCallback mediaProjectionCallback = null;
if (projection != null) {
mediaProjectionCallback = new MediaProjectionCallback(appToken);
@@ -313,6 +322,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
return mVirtualDisplayDevices.remove(appToken);
}
+ private static boolean isNeverBlank(int flags) {
+ // Private non-mirror displays are never blank and always on.
+ return (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0
+ && (flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0;
+ }
+
private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
private static final int PENDING_SURFACE_CHANGE = 0x01;
private static final int PENDING_RESIZE = 0x02;
@@ -340,6 +355,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private Display.Mode mMode;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
+ private final boolean mNeverBlank;
private final DisplayCutout mDisplayCutout;
private final float mDefaultBrightness;
private final float mDimBrightness;
@@ -371,7 +387,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ mNeverBlank = isNeverBlank(flags);
+ if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()
+ && !mNeverBlank) {
// The display's power state depends on the power state of the state of its
// display / power group, which we don't know here. Initializing to UNKNOWN allows
// the first call to requestDisplayStateLocked() to set the correct state.
@@ -471,7 +489,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
@Override
public Runnable requestDisplayStateLocked(int state, float brightnessState,
float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
+ Runnable runnable = null;
if (state != mDisplayState) {
+ Slog.d(TAG, "Changing state of virtual display " + mName + " from "
+ + Display.stateToString(mDisplayState) + " to "
+ + Display.stateToString(state));
+ if (state != Display.STATE_ON && state != Display.STATE_OFF) {
+ Slog.wtf(TAG, "Unexpected display state for Virtual Display: "
+ + Display.stateToString(state));
+ }
mDisplayState = state;
mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
@@ -480,6 +506,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
} else {
mCallback.dispatchDisplayResumed();
}
+
+ if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ final IBinder token = getDisplayTokenLocked();
+ runnable = () -> {
+ final int mode = getPowerModeForState(state);
+ Slog.d(TAG, "Requesting power mode for display " + mName + " to " + mode);
+ mSurfaceControlDisplayFactory.setDisplayPowerMode(token, mode);
+ };
+ }
}
if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
&& mBrightnessListener != null
@@ -488,7 +523,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mCurrentBrightness = brightnessState;
mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness);
}
- return null;
+ return runnable;
}
@Override
@@ -572,23 +607,14 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.yDpi = mDensityDpi;
mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame
mInfo.flags = 0;
- if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
- }
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
- }
- } else {
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
- | DisplayDeviceInfo.FLAG_NEVER_BLANK;
- }
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
- mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
- } else {
- mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
- }
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
+ }
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ }
+ if (mNeverBlank) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_NEVER_BLANK;
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
@@ -764,6 +790,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
*
* @param name The name of the display.
* @param secure Whether this display is secure.
+ * @param optimizeForPower Whether SurfaceFlinger should optimize for power (instead of
+ * performance). Such displays will depend on another display for
+ * it to be shown and rendered, and that display will optimize for
+ * performance when it is on.
* @param uniqueId The unique ID for the display.
* @param requestedRefreshRate
* The refresh rate, frames per second, to request on the virtual display.
@@ -773,8 +803,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
* the refresh rate of the leader physical display.
* @return The token reference for the display in SurfaceFlinger.
*/
- IBinder createDisplay(String name, boolean secure, String uniqueId,
- float requestedRefreshRate);
+ IBinder createDisplay(String name, boolean secure, boolean optimizeForPower,
+ String uniqueId, float requestedRefreshRate);
/**
* Destroy a display in SurfaceFlinger.
@@ -782,5 +812,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
* @param displayToken The display token for the display to be destroyed.
*/
void destroyDisplay(IBinder displayToken);
+
+ /**
+ * Set the display power mode in SurfaceFlinger.
+ *
+ * @param displayToken The display token for the display.
+ * @param mode the SurfaceControl power mode, e.g. {@link SurfaceControl#POWER_MODE_OFF}.
+ */
+ void setDisplayPowerMode(IBinder displayToken, int mode);
}
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 902eefa824b5..89679c728127 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -666,6 +666,12 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
+ if (getFeatureFlags().isDisplayContentModeManagementEnabled()) {
+ // The wifi display is allowed to switch content mode since FLAG_PRIVATE,
+ // FLAG_OWN_CONTENT_ONLY, and FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS are not
+ // enabled in WifiDisplayDevice#getDisplayDeviceInfoLocked().
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
mInfo.displayShape =
DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index a4804e1887fe..d4b9a6ce058b 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -49,11 +49,9 @@ public final class BrightnessReason {
public static final int MODIFIER_HDR = 0x4;
public static final int MODIFIER_THROTTLED = 0x8;
public static final int MODIFIER_MIN_LUX = 0x10;
- public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20;
- public static final int MODIFIER_STYLUS_UNDER_USE = 0x40;
+ public static final int MODIFIER_STYLUS_UNDER_USE = 0x20;
public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
- | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND
- | MODIFIER_STYLUS_UNDER_USE;
+ | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_STYLUS_UNDER_USE;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -157,9 +155,6 @@ public final class BrightnessReason {
if ((mModifier & MODIFIER_MIN_LUX) != 0) {
sb.append(" lux_lower_bound");
}
- if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) {
- sb.append(" user_min_pref");
- }
if ((mModifier & MODIFIER_STYLUS_UNDER_USE) != 0) {
sb.append(" stylus_under_use");
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index c3596c3e77fe..72cb31d8d1bb 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -19,12 +19,8 @@ package com.android.server.display.brightness.clamper;
import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
import android.hardware.display.DisplayManagerInternal;
-import android.net.Uri;
import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,7 +45,6 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
private static final String TAG = "BrightnessLowLuxModifier";
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
private static final float MIN_NITS_DEFAULT = 0.2f;
- private final SettingsObserver mSettingsObserver;
private final ContentResolver mContentResolver;
private final Handler mHandler;
private final BrightnessClamperController.ClamperChangeListener mChangeListener;
@@ -69,7 +64,6 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
mChangeListener = listener;
mHandler = handler;
mContentResolver = context.getContentResolver();
- mSettingsObserver = new SettingsObserver(mHandler);
mDisplayDeviceConfig = displayDeviceConfig;
mHandler.post(() -> {
start();
@@ -82,12 +76,7 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
*/
@VisibleForTesting
public void recalculateLowerBound() {
- float settingNitsLowerBound = Settings.Secure.getFloatForUser(
- mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
- /* def= */ MIN_NITS_DEFAULT, UserHandle.USER_CURRENT);
-
- boolean isActive = isSettingEnabled()
- && mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
+ boolean isActive = mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
final int reason;
float minNitsAllowed = -1f; // undefined, if setting is off.
@@ -95,12 +84,9 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
if (isActive) {
float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
- minNitsAllowed = Math.max(settingNitsLowerBound,
- luxBasedNitsLowerBound);
+ minNitsAllowed = Math.max(MIN_NITS_DEFAULT, luxBasedNitsLowerBound);
minBrightnessAllowed = getBrightnessFromNits(minNitsAllowed);
- reason = settingNitsLowerBound > luxBasedNitsLowerBound
- ? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
- : BrightnessReason.MODIFIER_MIN_LUX;
+ reason = BrightnessReason.MODIFIER_MIN_LUX;
} else {
minBrightnessAllowed = mDisplayDeviceConfig.getEvenDimmerTransitionPoint();
reason = 0;
@@ -169,7 +155,6 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
@Override
public void apply(DisplayManagerInternal.DisplayPowerRequest request,
DisplayBrightnessState.Builder stateBuilder) {
-
stateBuilder.setMinBrightness(mBrightnessLowerBound);
float boundedBrightness = Math.max(mBrightnessLowerBound, stateBuilder.getBrightness());
stateBuilder.setBrightness(boundedBrightness);
@@ -181,12 +166,11 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
@Override
public void stop() {
- mContentResolver.unregisterContentObserver(mSettingsObserver);
}
@Override
public boolean shouldListenToLightSensor() {
- return isSettingEnabled();
+ return true;
}
@Override
@@ -204,37 +188,8 @@ public class BrightnessLowLuxModifier extends BrightnessModifier implements
pw.println(" mMinNitsAllowed=" + mMinNitsAllowed);
}
- /**
- * Defaults to true, on devices where setting is unset.
- *
- * @return if setting indicates feature is enabled
- */
- private boolean isSettingEnabled() {
- return Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- /* def= */ 1.0f, UserHandle.USER_CURRENT) == 1.0f;
- }
-
private float getBrightnessFromNits(float nits) {
return mDisplayDeviceConfig.getBrightnessFromBacklight(
mDisplayDeviceConfig.getBacklightFromNits(nits));
}
-
- private final class SettingsObserver extends ContentObserver {
-
- SettingsObserver(Handler handler) {
- super(handler);
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_MIN_NITS),
- false, this, UserHandle.USER_ALL);
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_ACTIVATED),
- false, this, UserHandle.USER_ALL);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- recalculateLowerBound();
- }
- }
}
diff --git a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
index 2751835f9958..50782a2f22c8 100644
--- a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
+++ b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
@@ -16,9 +16,11 @@
package com.android.server.display.mode;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
import android.os.Looper;
+import android.util.LongSparseArray;
import android.util.Slog;
-import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayEventReceiver;
@@ -34,72 +36,128 @@ final class ModeChangeObserver {
@SuppressWarnings("unused")
private DisplayEventReceiver mModeChangeListener;
- private final SparseArray<Set<Integer>> mRejectedModesByDisplay = new SparseArray<>();
- private Looper mLooper;
+ private DisplayManager.DisplayListener mDisplayListener;
+ private final LongSparseArray<Set<Integer>> mRejectedModesMap =
+ new LongSparseArray<>();
+ private final LongSparseArray<Integer> mPhysicalIdToLogicalIdMap = new LongSparseArray<>();
+ private final Looper mLooper;
+ private final Handler mHandler;
+ /**
+ * Observer for display mode changes.
+ * This class observes display mode rejections and updates the vote storage
+ * for rejected modes vote accordingly.
+ */
ModeChangeObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector,
Looper looper) {
mVotesStorage = votesStorage;
mInjector = injector;
mLooper = looper;
+ mHandler = new Handler(mLooper);
}
+ /**
+ * Start observing display mode changes.
+ */
void observe() {
- mModeChangeListener = new DisplayEventReceiver(mLooper) {
+ updatePhysicalIdToLogicalIdMap();
+ mDisplayListener = new DisplayManager.DisplayListener() {
@Override
- public void onModeRejected(long physicalDisplayId, int modeId) {
- Slog.d(TAG, "Mode Rejected event received");
- int displayId = getLogicalDisplayId(physicalDisplayId);
- if (displayId < 0) {
- Slog.e(TAG, "Logical Display Id not found");
+ public void onDisplayAdded(int displayId) {
+ updateVoteForDisplay(displayId);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ int oldPhysicalDisplayIdIndex = mPhysicalIdToLogicalIdMap.indexOfValue(displayId);
+ if (oldPhysicalDisplayIdIndex < 0) {
+ Slog.e(TAG, "Removed display not found");
return;
}
- populateRejectedModesListByDisplay(displayId, modeId);
+ long oldPhysicalDisplayId =
+ mPhysicalIdToLogicalIdMap.keyAt(oldPhysicalDisplayIdIndex);
+ mPhysicalIdToLogicalIdMap.delete(oldPhysicalDisplayId);
+ mRejectedModesMap.delete(oldPhysicalDisplayId);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, null);
}
@Override
- public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
- Slog.d(TAG, "Hotplug event received");
- if (!connected) {
- int displayId = getLogicalDisplayId(physicalDisplayId);
- if (displayId < 0) {
- Slog.e(TAG, "Logical Display Id not found");
- return;
- }
- clearRejectedModesListByDisplay(displayId);
+ public void onDisplayChanged(int displayId) {
+ int oldPhysicalDisplayIdIndex = mPhysicalIdToLogicalIdMap.indexOfValue(displayId);
+ if (oldPhysicalDisplayIdIndex < 0) {
+ Slog.e(TAG, "Changed display not found");
+ return;
+ }
+ long oldPhysicalDisplayId =
+ mPhysicalIdToLogicalIdMap.keyAt(oldPhysicalDisplayIdIndex);
+ mPhysicalIdToLogicalIdMap.delete(oldPhysicalDisplayId);
+
+ updateVoteForDisplay(displayId);
+ }
+ };
+ mInjector.registerDisplayListener(mDisplayListener, mHandler,
+ DisplayManager.EVENT_TYPE_DISPLAY_ADDED
+ | DisplayManager.EVENT_TYPE_DISPLAY_CHANGED
+ | DisplayManager.EVENT_TYPE_DISPLAY_REMOVED);
+ mModeChangeListener = new DisplayEventReceiver(mLooper) {
+ @Override
+ public void onModeRejected(long physicalDisplayId, int modeId) {
+ Slog.d(TAG, "Mode Rejected event received");
+ updateRejectedModesListByDisplay(physicalDisplayId, modeId);
+ if (mPhysicalIdToLogicalIdMap.indexOfKey(physicalDisplayId) < 0) {
+ Slog.d(TAG, "Rejected Modes Vote will be updated after display is added");
+ return;
}
+ mVotesStorage.updateVote(mPhysicalIdToLogicalIdMap.get(physicalDisplayId),
+ Vote.PRIORITY_REJECTED_MODES,
+ Vote.forRejectedModes(mRejectedModesMap.get(physicalDisplayId)));
}
};
}
- private int getLogicalDisplayId(long rejectedModePhysicalDisplayId) {
+ private void updateVoteForDisplay(int displayId) {
+ Display display = mInjector.getDisplay(displayId);
+ if (display == null) {
+ // We can occasionally get a display added or changed event for a display that was
+ // subsequently removed, which means this returns null. Check this case and bail
+ // out early; if it gets re-attached we will eventually get another call back for it.
+ Slog.e(TAG, "Added or Changed display has disappeared");
+ return;
+ }
+ DisplayAddress address = display.getAddress();
+ if (address instanceof DisplayAddress.Physical physical) {
+ long physicalDisplayId = physical.getPhysicalDisplayId();
+ mPhysicalIdToLogicalIdMap.put(physicalDisplayId, displayId);
+ Set<Integer> modes = mRejectedModesMap.get(physicalDisplayId);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES,
+ modes != null ? Vote.forRejectedModes(modes) : null);
+ }
+ }
+
+ private void updatePhysicalIdToLogicalIdMap() {
Display[] displays = mInjector.getDisplays();
for (Display display : displays) {
+ if (display == null) {
+ continue;
+ }
DisplayAddress address = display.getAddress();
if (address instanceof DisplayAddress.Physical physical) {
- long physicalDisplayId = physical.getPhysicalDisplayId();
- if (physicalDisplayId == rejectedModePhysicalDisplayId) {
- return display.getDisplayId();
- }
+ mPhysicalIdToLogicalIdMap.put(physical.getPhysicalDisplayId(),
+ display.getDisplayId());
}
}
- return -1;
}
- private void populateRejectedModesListByDisplay(int displayId, int rejectedModeId) {
- Set<Integer> alreadyRejectedModes = mRejectedModesByDisplay.get(displayId);
+ private void updateRejectedModesListByDisplay(long rejectedModePhysicalDisplayId,
+ int rejectedModeId) {
+ Set<Integer> alreadyRejectedModes =
+ mRejectedModesMap.get(rejectedModePhysicalDisplayId);
if (alreadyRejectedModes == null) {
alreadyRejectedModes = new HashSet<>();
- mRejectedModesByDisplay.put(displayId, alreadyRejectedModes);
+ mRejectedModesMap.put(rejectedModePhysicalDisplayId,
+ alreadyRejectedModes);
}
alreadyRejectedModes.add(rejectedModeId);
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES,
- Vote.forRejectedModes(alreadyRejectedModes));
- }
-
- private void clearRejectedModesListByDisplay(int displayId) {
- mRejectedModesByDisplay.remove(displayId);
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, null);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 3cb21c3e2697..8f5b831ca0b4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -219,7 +219,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
&& reason != HdmiControlService.INITIATED_BY_BOOT_UP;
List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer
.getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE);
- if (bufferedActiveSource.isEmpty()) {
+ List<HdmiCecMessage> bufferedActiveSourceFromService = mService.getCecMessageWithOpcode(
+ Constants.MESSAGE_ACTIVE_SOURCE);
+ if (bufferedActiveSource.isEmpty() && bufferedActiveSourceFromService.isEmpty()) {
addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
@@ -793,7 +795,7 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
- if (!isInputReady(info.getDeviceId())) {
+ if (!isInputReady(info.getId())) {
mService.getHdmiCecNetwork().removeCecDevice(
HdmiCecLocalDeviceTv.this, info.getLogicalAddress());
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6d973ac8d1b5..fdd0ef2f90e1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1593,6 +1593,17 @@ public class HdmiControlService extends SystemService {
this.mCecMessageBuffer = cecMessageBuffer;
}
+ List<HdmiCecMessage> getCecMessageWithOpcode(int opcode) {
+ List<HdmiCecMessage> cecMessagesWithOpcode = new ArrayList<>();
+ List<HdmiCecMessage> cecMessages = mCecMessageBuffer.getBuffer();
+ for (HdmiCecMessage message: cecMessages) {
+ if (message.getOpcode() == opcode) {
+ cecMessagesWithOpcode.add(message);
+ }
+ }
+ return cecMessagesWithOpcode;
+ }
+
/**
* Returns {@link Looper} of main thread. Use this {@link Looper} instance
* for tasks that are running on main service thread.
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 6e5e0fd5224f..af065861f1ae 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -193,6 +193,8 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
@Nullable
private UserManagerInternal mUm;
+ private final MyPackageMonitor mPackageMonitor;
+
/**
* Default constructor.
*
@@ -289,6 +291,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
}
});
}
+ mPackageMonitor = new MyPackageMonitor(/* supportsPackageRestartQuery */ true);
startTrackingPackageChanges();
}
@@ -986,260 +989,264 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
}
}
- private void startTrackingPackageChanges() {
- final PackageMonitor monitor = new PackageMonitor(true) {
+ private class MyPackageMonitor extends PackageMonitor {
+ MyPackageMonitor(boolean supportsPackageRestartQuery) {
+ super(supportsPackageRestartQuery);
+ }
- @Override
- public void onPackageUpdateStarted(@NonNull String packageName, int uid) {
- if (verbose) Slog.v(mTag, "onPackageUpdateStarted(): " + packageName);
+ @Override
+ public void onPackageUpdateStarted(@NonNull String packageName, int uid) {
+ if (verbose) Slog.v(mTag, "onPackageUpdateStarted(): " + packageName);
+ synchronized (mLock) {
final String activePackageName = getActiveServicePackageNameLocked();
if (!packageName.equals(activePackageName)) return;
final int userId = getChangingUserId();
- synchronized (mLock) {
- if (mUpdatingPackageNames == null) {
- mUpdatingPackageNames = new SparseArray<String>(mServicesCacheList.size());
+
+ if (mUpdatingPackageNames == null) {
+ mUpdatingPackageNames = new SparseArray<String>(mServicesCacheList.size());
+ }
+ mUpdatingPackageNames.put(userId, packageName);
+ onServicePackageUpdatingLocked(userId);
+ if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_NO_REFRESH) != 0) {
+ if (debug) {
+ Slog.d(mTag, "Holding service for user " + userId + " while package "
+ + activePackageName + " is being updated");
}
- mUpdatingPackageNames.put(userId, packageName);
- onServicePackageUpdatingLocked(userId);
- if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_NO_REFRESH) != 0) {
- if (debug) {
- Slog.d(mTag, "Holding service for user " + userId + " while package "
- + activePackageName + " is being updated");
- }
- } else {
- if (debug) {
- Slog.d(mTag, "Removing service for user " + userId
- + " because package " + activePackageName
- + " is being updated");
- }
- removeCachedServiceListLocked(userId);
+ } else {
+ if (debug) {
+ Slog.d(mTag, "Removing service for user " + userId
+ + " because package " + activePackageName
+ + " is being updated");
+ }
+ removeCachedServiceListLocked(userId);
- if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_REFRESH_EAGER)
- != 0) {
- if (debug) {
- Slog.d(mTag, "Eagerly recreating service for user "
- + userId);
- }
- getServiceForUserLocked(userId);
+ if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_REFRESH_EAGER)
+ != 0) {
+ if (debug) {
+ Slog.d(mTag, "Eagerly recreating service for user "
+ + userId);
}
+ getServiceForUserLocked(userId);
}
}
}
+ }
- @Override
- public void onPackageUpdateFinished(@NonNull String packageName, int uid) {
- if (verbose) Slog.v(mTag, "onPackageUpdateFinished(): " + packageName);
- final int userId = getChangingUserId();
- synchronized (mLock) {
- final String activePackageName = mUpdatingPackageNames == null ? null
- : mUpdatingPackageNames.get(userId);
- if (packageName.equals(activePackageName)) {
- if (mUpdatingPackageNames != null) {
- mUpdatingPackageNames.remove(userId);
- if (mUpdatingPackageNames.size() == 0) {
- mUpdatingPackageNames = null;
- }
+ @Override
+ public void onPackageUpdateFinished(@NonNull String packageName, int uid) {
+ if (verbose) Slog.v(mTag, "onPackageUpdateFinished(): " + packageName);
+ final int userId = getChangingUserId();
+ synchronized (mLock) {
+ final String activePackageName = mUpdatingPackageNames == null ? null
+ : mUpdatingPackageNames.get(userId);
+ if (packageName.equals(activePackageName)) {
+ if (mUpdatingPackageNames != null) {
+ mUpdatingPackageNames.remove(userId);
+ if (mUpdatingPackageNames.size() == 0) {
+ mUpdatingPackageNames = null;
}
- onServicePackageUpdatedLocked(userId);
- } else {
- handlePackageUpdateLocked(packageName);
}
+ onServicePackageUpdatedLocked(userId);
+ } else {
+ handlePackageUpdateLocked(packageName);
}
}
+ }
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- if (mServiceNameResolver != null
- && mServiceNameResolver.isConfiguredInMultipleMode()) {
- final int userId = getChangingUserId();
- synchronized (mLock) {
- handlePackageRemovedMultiModeLocked(packageName, userId);
- }
- return;
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ if (mServiceNameResolver != null
+ && mServiceNameResolver.isConfiguredInMultipleMode()) {
+ final int userId = getChangingUserId();
+ synchronized (mLock) {
+ handlePackageRemovedMultiModeLocked(packageName, userId);
}
+ return;
+ }
- synchronized (mLock) {
- final int userId = getChangingUserId();
- final S service = peekServiceForUserLocked(userId);
- if (service != null) {
- final ComponentName componentName = service.getServiceComponentName();
- if (componentName != null) {
- if (packageName.equals(componentName.getPackageName())) {
- handleActiveServiceRemoved(userId);
- }
+ synchronized (mLock) {
+ final int userId = getChangingUserId();
+ final S service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ final ComponentName componentName = service.getServiceComponentName();
+ if (componentName != null) {
+ if (packageName.equals(componentName.getPackageName())) {
+ handleActiveServiceRemoved(userId);
}
}
}
}
+ }
- @Override
- public boolean onHandleForceStop(Intent intent, String[] packages,
- int uid, boolean doit) {
- synchronized (mLock) {
- final String activePackageName = getActiveServicePackageNameLocked();
- for (String pkg : packages) {
- if (pkg.equals(activePackageName)) {
- if (!doit) {
- return true;
- }
- final String action = intent.getAction();
- final int userId = getChangingUserId();
- if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
- handleActiveServiceRestartedLocked(activePackageName, userId);
- } else {
- removeCachedServiceListLocked(userId);
- }
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages,
+ int uid, boolean doit) {
+ synchronized (mLock) {
+ final String activePackageName = getActiveServicePackageNameLocked();
+ for (String pkg : packages) {
+ if (pkg.equals(activePackageName)) {
+ if (!doit) {
+ return true;
+ }
+ final String action = intent.getAction();
+ final int userId = getChangingUserId();
+ if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
+ handleActiveServiceRestartedLocked(activePackageName, userId);
} else {
- handlePackageUpdateLocked(pkg);
+ removeCachedServiceListLocked(userId);
}
+ } else {
+ handlePackageUpdateLocked(pkg);
}
}
- return false;
}
+ return false;
+ }
- @Override
- public void onPackageDataCleared(String packageName, int uid) {
- if (verbose) Slog.v(mTag, "onPackageDataCleared(): " + packageName);
- final int userId = getChangingUserId();
+ @Override
+ public void onPackageDataCleared(String packageName, int uid) {
+ if (verbose) Slog.v(mTag, "onPackageDataCleared(): " + packageName);
+ final int userId = getChangingUserId();
- if (mServiceNameResolver != null
- && mServiceNameResolver.isConfiguredInMultipleMode()) {
- synchronized (mLock) {
- onServicePackageDataClearedMultiModeLocked(packageName, userId);
- }
- return;
+ if (mServiceNameResolver != null
+ && mServiceNameResolver.isConfiguredInMultipleMode()) {
+ synchronized (mLock) {
+ onServicePackageDataClearedMultiModeLocked(packageName, userId);
}
+ return;
+ }
- synchronized (mLock) {
- final S service = peekServiceForUserLocked(userId);
- if (service != null) {
- final ComponentName componentName = service.getServiceComponentName();
- if (componentName != null) {
- if (packageName.equals(componentName.getPackageName())) {
- onServicePackageDataClearedLocked(userId);
- }
+ synchronized (mLock) {
+ final S service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ final ComponentName componentName = service.getServiceComponentName();
+ if (componentName != null) {
+ if (packageName.equals(componentName.getPackageName())) {
+ onServicePackageDataClearedLocked(userId);
}
}
}
}
+ }
- private void handleActiveServiceRemoved(@UserIdInt int userId) {
- synchronized (mLock) {
- removeCachedServiceListLocked(userId);
+ private void handleActiveServiceRemoved(@UserIdInt int userId) {
+ synchronized (mLock) {
+ removeCachedServiceListLocked(userId);
+ }
+ final String serviceSettingsProperty = getServiceSettingsProperty();
+ if (serviceSettingsProperty != null) {
+ Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ serviceSettingsProperty, null, userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void handleActiveServiceRestartedLocked(String activePackageName,
+ @UserIdInt int userId) {
+ if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_NO_REFRESH) != 0) {
+ if (debug) {
+ Slog.d(mTag, "Holding service for user " + userId + " while package "
+ + activePackageName + " is being restarted");
}
- final String serviceSettingsProperty = getServiceSettingsProperty();
- if (serviceSettingsProperty != null) {
- Settings.Secure.putStringForUser(getContext().getContentResolver(),
- serviceSettingsProperty, null, userId);
+ } else {
+ if (debug) {
+ Slog.d(mTag, "Removing service for user " + userId
+ + " because package " + activePackageName
+ + " is being restarted");
}
- }
+ removeCachedServiceListLocked(userId);
- @GuardedBy("mLock")
- private void handleActiveServiceRestartedLocked(String activePackageName,
- @UserIdInt int userId) {
- if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_NO_REFRESH) != 0) {
+ if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_REFRESH_EAGER) != 0) {
if (debug) {
- Slog.d(mTag, "Holding service for user " + userId + " while package "
- + activePackageName + " is being restarted");
- }
- } else {
- if (debug) {
- Slog.d(mTag, "Removing service for user " + userId
- + " because package " + activePackageName
- + " is being restarted");
- }
- removeCachedServiceListLocked(userId);
-
- if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_REFRESH_EAGER) != 0) {
- if (debug) {
- Slog.d(mTag, "Eagerly recreating service for user " + userId);
- }
- updateCachedServiceLocked(userId);
+ Slog.d(mTag, "Eagerly recreating service for user " + userId);
}
+ updateCachedServiceLocked(userId);
}
- onServicePackageRestartedLocked(userId);
}
+ onServicePackageRestartedLocked(userId);
+ }
- @Override
- public void onPackageModified(String packageName) {
- synchronized (mLock) {
- if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
+ @Override
+ public void onPackageModified(String packageName) {
+ synchronized (mLock) {
+ if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
- if (mServiceNameResolver == null) {
- return;
- }
+ if (mServiceNameResolver == null) {
+ return;
+ }
- final int userId = getChangingUserId();
- final String[] serviceNames = mServiceNameResolver.getDefaultServiceNameList(
- userId);
- if (serviceNames != null) {
- if (Flags.packageUpdateFixEnabled()) {
- if (mServiceNameResolver.isConfiguredInMultipleMode()) {
- // Remove any service that is in the cache but is no longer valid
- // after this modification for this particular package
- removeInvalidCachedServicesLocked(serviceNames, packageName,
- userId);
- }
+ final int userId = getChangingUserId();
+ final String[] serviceNames = mServiceNameResolver.getDefaultServiceNameList(
+ userId);
+ if (serviceNames != null) {
+ if (Flags.packageUpdateFixEnabled()) {
+ if (mServiceNameResolver.isConfiguredInMultipleMode()) {
+ // Remove any service that is in the cache but is no longer valid
+ // after this modification for this particular package
+ removeInvalidCachedServicesLocked(serviceNames, packageName,
+ userId);
}
+ }
- // Update services that are still valid
- for (int i = 0; i < serviceNames.length; i++) {
- peekAndUpdateCachedServiceLocked(packageName, userId,
- serviceNames[i]);
- }
+ // Update services that are still valid
+ for (int i = 0; i < serviceNames.length; i++) {
+ peekAndUpdateCachedServiceLocked(packageName, userId,
+ serviceNames[i]);
}
}
}
+ }
- @GuardedBy("mLock")
- private void peekAndUpdateCachedServiceLocked(String packageName, int userId,
- String serviceName) {
- if (serviceName == null) {
- return;
- }
-
- final ComponentName serviceComponentName =
- ComponentName.unflattenFromString(serviceName);
- if (serviceComponentName == null
- || !serviceComponentName.getPackageName().equals(packageName)) {
- return;
- }
+ @GuardedBy("mLock")
+ private void peekAndUpdateCachedServiceLocked(String packageName, int userId,
+ String serviceName) {
+ if (serviceName == null) {
+ return;
+ }
- // The default service package has changed, update the cached if the service
- // exists but no active component.
- final S service = peekServiceForUserLocked(userId);
- if (service != null) {
- final ComponentName componentName = service.getServiceComponentName();
- if (componentName == null) {
- if (verbose) Slog.v(mTag, "update cached");
- updateCachedServiceLocked(userId);
- }
- }
+ final ComponentName serviceComponentName =
+ ComponentName.unflattenFromString(serviceName);
+ if (serviceComponentName == null
+ || !serviceComponentName.getPackageName().equals(packageName)) {
+ return;
}
- @GuardedBy("mLock")
- private String getActiveServicePackageNameLocked() {
- final int userId = getChangingUserId();
- final S service = peekServiceForUserLocked(userId);
- if (service == null) {
- return null;
- }
- final ComponentName serviceComponent = service.getServiceComponentName();
- if (serviceComponent == null) {
- return null;
+ // The default service package has changed, update the cached if the service
+ // exists but no active component.
+ final S service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ final ComponentName componentName = service.getServiceComponentName();
+ if (componentName == null) {
+ if (verbose) Slog.v(mTag, "update cached");
+ updateCachedServiceLocked(userId);
}
- return serviceComponent.getPackageName();
}
+ }
- @GuardedBy("mLock")
- private void handlePackageUpdateLocked(String packageName) {
- visitServicesLocked((s) -> s.handlePackageUpdateLocked(packageName));
+ @GuardedBy("mLock")
+ private String getActiveServicePackageNameLocked() {
+ final int userId = getChangingUserId();
+ final S service = peekServiceForUserLocked(userId);
+ if (service == null) {
+ return null;
+ }
+ final ComponentName serviceComponent = service.getServiceComponentName();
+ if (serviceComponent == null) {
+ return null;
}
- };
+ return serviceComponent.getPackageName();
+ }
+ @GuardedBy("mLock")
+ private void handlePackageUpdateLocked(String packageName) {
+ visitServicesLocked((s) -> s.handlePackageUpdateLocked(packageName));
+ }
+ }
+
+ private void startTrackingPackageChanges() {
// package changes
- monitor.register(getContext(), null, UserHandle.ALL, true);
+ mPackageMonitor.register(getContext(), null, UserHandle.ALL, true);
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 7f853844c326..67e1ccc6a850 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -138,11 +138,6 @@ final class InputGestureManager {
KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
),
createKeyGesture(
- KeyEvent.KEYCODE_DEL,
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK
- ),
- createKeyGesture(
KeyEvent.KEYCODE_ESCAPE,
KeyEvent.META_META_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_BACK
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c2fecf283a34..d9db178e0dc2 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -568,6 +568,7 @@ public class InputManagerService extends IInputManager.Stub
}
mWindowManagerCallbacks = callbacks;
registerLidSwitchCallbackInternal(mWindowManagerCallbacks);
+ mKeyGestureController.setWindowManagerCallbacks(callbacks);
}
public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) {
@@ -2756,24 +2757,6 @@ public class InputManagerService extends IInputManager.Stub
@Nullable IBinder focussedToken) {
return InputManagerService.this.handleKeyGestureEvent(event);
}
-
- @Override
- public boolean isKeyGestureSupported(int gestureType) {
- switch (gestureType) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
- case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS:
- return true;
- default:
- return false;
-
- }
- }
});
}
@@ -3371,6 +3354,11 @@ public class InputManagerService extends IInputManager.Stub
*/
@Nullable
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
+
+ /**
+ * Provide information on whether the keyguard is currently locked or not.
+ */
+ boolean isKeyguardLocked(int displayId);
}
/**
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index ef5babf19d83..395c77322c04 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -62,8 +62,10 @@ import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.ViewConfiguration;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
@@ -104,6 +106,7 @@ final class KeyGestureController {
private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1;
private static final int MSG_PERSIST_CUSTOM_GESTURES = 2;
private static final int MSG_LOAD_CUSTOM_GESTURES = 3;
+ private static final int MSG_ACCESSIBILITY_SHORTCUT = 4;
// must match: config_settingsKeyBehavior in config.xml
private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0;
@@ -122,12 +125,15 @@ final class KeyGestureController {
static final int POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS = 2;
private final Context mContext;
+ private InputManagerService.WindowManagerCallbacks mWindowManagerCallbacks;
private final Handler mHandler;
private final Handler mIoHandler;
private final int mSystemPid;
private final KeyCombinationManager mKeyCombinationManager;
private final SettingsObserver mSettingsObserver;
private final AppLaunchShortcutManager mAppLaunchShortcutManager;
+ @VisibleForTesting
+ final AccessibilityShortcutController mAccessibilityShortcutController;
private final InputGestureManager mInputGestureManager;
private final DisplayManager mDisplayManager;
@GuardedBy("mInputDataStore")
@@ -175,8 +181,14 @@ final class KeyGestureController {
private final boolean mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled();
- KeyGestureController(Context context, Looper looper, Looper ioLooper,
+ public KeyGestureController(Context context, Looper looper, Looper ioLooper,
InputDataStore inputDataStore) {
+ this(context, looper, ioLooper, inputDataStore, new Injector());
+ }
+
+ @VisibleForTesting
+ KeyGestureController(Context context, Looper looper, Looper ioLooper,
+ InputDataStore inputDataStore, Injector injector) {
mContext = context;
mHandler = new Handler(looper, this::handleMessage);
mIoHandler = new Handler(ioLooper, this::handleIoMessage);
@@ -197,6 +209,8 @@ final class KeyGestureController {
mSettingsObserver = new SettingsObserver(mHandler);
mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
mInputGestureManager = new InputGestureManager(mContext);
+ mAccessibilityShortcutController = injector.getAccessibilityShortcutController(mContext,
+ mHandler);
mDisplayManager = Objects.requireNonNull(mContext.getSystemService(DisplayManager.class));
mInputDataStore = inputDataStore;
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -295,8 +309,8 @@ final class KeyGestureController {
KeyEvent.KEYCODE_VOLUME_UP) {
@Override
public boolean preCondition() {
- return isKeyGestureSupported(
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD);
+ return mAccessibilityShortcutController.isAccessibilityShortcutAvailable(
+ mWindowManagerCallbacks.isKeyguardLocked(DEFAULT_DISPLAY));
}
@Override
@@ -376,15 +390,15 @@ final class KeyGestureController {
KeyEvent.KEYCODE_DPAD_DOWN) {
@Override
public boolean preCondition() {
- return isKeyGestureSupported(
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD);
+ return mAccessibilityShortcutController
+ .isAccessibilityShortcutAvailable(false);
}
@Override
public void execute() {
handleMultiKeyGesture(
new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
KeyGestureEvent.ACTION_GESTURE_START, 0);
}
@@ -392,7 +406,7 @@ final class KeyGestureController {
public void cancel() {
handleMultiKeyGesture(
new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
KeyGestureEvent.ACTION_GESTURE_COMPLETE,
KeyGestureEvent.FLAG_CANCELLED);
}
@@ -438,6 +452,7 @@ final class KeyGestureController {
mSettingsObserver.observe();
mAppLaunchShortcutManager.systemRunning();
mInputGestureManager.systemRunning();
+ initKeyGestures();
int userId;
synchronized (mUserLock) {
@@ -447,6 +462,27 @@ final class KeyGestureController {
mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget();
}
+ @SuppressLint("MissingPermission")
+ private void initKeyGestures() {
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ im.registerKeyGestureEventHandler((event, focusedToken) -> {
+ switch (event.getKeyGestureType()) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
+ if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) {
+ mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
+ getAccessibilityShortcutTimeout());
+ } else {
+ mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
+ }
+ return true;
+ default:
+ return false;
+ }
+ });
+ }
+
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
if (mVisibleBackgroundUsersEnabled && shouldIgnoreKeyEventForVisibleBackgroundUser(event)) {
return false;
@@ -971,17 +1007,6 @@ final class KeyGestureController {
return false;
}
- private boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) {
- synchronized (mKeyGestureHandlerRecords) {
- for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) {
- if (handler.isKeyGestureSupported(gestureType)) {
- return true;
- }
- }
- }
- return false;
- }
-
public void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
@KeyGestureEvent.KeyGestureType int gestureType) {
// TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally
@@ -1019,9 +1044,16 @@ final class KeyGestureController {
synchronized (mUserLock) {
mCurrentUserId = userId;
}
+ mAccessibilityShortcutController.setCurrentUser(userId);
mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget();
}
+
+ public void setWindowManagerCallbacks(
+ @NonNull InputManagerService.WindowManagerCallbacks callbacks) {
+ mWindowManagerCallbacks = callbacks;
+ }
+
private boolean isDefaultDisplayOn() {
Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
if (defaultDisplay == null) {
@@ -1068,6 +1100,9 @@ final class KeyGestureController {
AidlKeyGestureEvent event = (AidlKeyGestureEvent) msg.obj;
notifyKeyGestureEvent(event);
break;
+ case MSG_ACCESSIBILITY_SHORTCUT:
+ mAccessibilityShortcutController.performAccessibilityShortcut();
+ break;
}
return true;
}
@@ -1347,17 +1382,6 @@ final class KeyGestureController {
}
return false;
}
-
- public boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) {
- try {
- return mKeyGestureHandler.isKeyGestureSupported(gestureType);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to identify if key gesture type is supported by the "
- + "process " + mPid + ", assuming it died.", ex);
- binderDied();
- }
- return false;
- }
}
private class SettingsObserver extends ContentObserver {
@@ -1413,6 +1437,25 @@ final class KeyGestureController {
return event;
}
+ private long getAccessibilityShortcutTimeout() {
+ synchronized (mUserLock) {
+ final ViewConfiguration config = ViewConfiguration.get(mContext);
+ final boolean hasDialogShown = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mCurrentUserId) != 0;
+ final boolean skipTimeoutRestriction =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
+ 0, mCurrentUserId) != 0;
+
+ // If users manually set the volume key shortcut for any accessibility service, the
+ // system would bypass the timeout restriction of the shortcut dialog.
+ return hasDialogShown || skipTimeoutRestriction
+ ? config.getAccessibilityShortcutKeyTimeoutAfterConfirmation()
+ : config.getAccessibilityShortcutKeyTimeout();
+ }
+ }
+
public void dump(IndentingPrintWriter ipw) {
ipw.println("KeyGestureController:");
ipw.increaseIndent();
@@ -1459,4 +1502,12 @@ final class KeyGestureController {
mAppLaunchShortcutManager.dump(ipw);
mInputGestureManager.dump(ipw);
}
+
+ @VisibleForTesting
+ static class Injector {
+ AccessibilityShortcutController getAccessibilityShortcutController(Context context,
+ Handler handler) {
+ return new AccessibilityShortcutController(context, handler, UserHandle.USER_SYSTEM);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 600cf7f06981..1a4ead22f658 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -22,6 +22,7 @@ import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IM
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS;
import static com.android.server.EventLogTags.IMF_HIDE_IME;
import static com.android.server.EventLogTags.IMF_SHOW_IME;
+import static com.android.server.inputmethod.ImeProtoLogGroup.IME_VISIBILITY_APPLIER_DEBUG;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
@@ -36,7 +37,6 @@ import android.annotation.UserIdInt;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.EventLog;
-import android.util.Slog;
import android.view.MotionEvent;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
@@ -46,6 +46,7 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.protolog.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.wm.ImeTargetVisibilityPolicy;
import com.android.server.wm.WindowManagerInternal;
@@ -58,9 +59,7 @@ import java.util.Objects;
*/
final class DefaultImeVisibilityApplier {
- private static final String TAG = "DefaultImeVisibilityApplier";
-
- private static final boolean DEBUG = InputMethodManagerService.DEBUG;
+ static final String TAG = "DefaultImeVisibilityApplier";
private InputMethodManagerService mService;
@@ -93,11 +92,10 @@ final class DefaultImeVisibilityApplier {
final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
- if (DEBUG) {
- Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
- + ", " + showFlags + ", " + resultReceiver + ") for reason: "
- + InputMethodDebug.softInputDisplayReasonToString(reason));
- }
+ ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG,
+ "Calling %s.showSoftInput(%s, %s, %s) for reason: %s", curMethod,
+ showInputToken, showFlags, resultReceiver,
+ InputMethodDebug.softInputDisplayReasonToString(reason));
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
if (DEBUG_IME_VISIBILITY) {
@@ -136,11 +134,9 @@ final class DefaultImeVisibilityApplier {
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
- if (DEBUG) {
- Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
- + ", " + resultReceiver + ") for reason: "
- + InputMethodDebug.softInputDisplayReasonToString(reason));
- }
+ ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG,
+ "Calling %s.hideSoftInput(0, %s, %s) for reason: %s", curMethod, hideInputToken,
+ resultReceiver, InputMethodDebug.softInputDisplayReasonToString(reason));
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
if (DEBUG_IME_VISIBILITY) {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 02987a98417f..15f186b047f2 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -132,8 +132,8 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
@Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod);
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod);
InputBindResult startInputOrWindowGainedFocus(
@StartInputReason int startInputReason, IInputMethodClient client,
@@ -142,7 +142,7 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
@Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher);
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible);
void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
@@ -324,11 +324,11 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
return mCallback.startInputOrWindowGainedFocus(
startInputReason, client, windowToken, startInputFlags, softInputMode,
windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, imeRequestedVisible);
}
@Override
@@ -340,13 +340,13 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod) {
mCallback.startInputOrWindowGainedFocusAsync(
startInputReason, client, windowToken, startInputFlags, softInputMode,
windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq,
- useAsyncShowHideMethod);
+ unverifiedTargetSdkVersion, userId, imeDispatcher, imeRequestedVisible,
+ startInputSeq, useAsyncShowHideMethod);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java b/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java
index f9a56effc800..ea4e29564cc0 100644
--- a/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java
+++ b/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java
@@ -23,7 +23,11 @@ import java.util.UUID;
public enum ImeProtoLogGroup implements IProtoLogGroup {
// TODO(b/393561240): add info/warn/error log level and replace in IMMS
IMMS_DEBUG(Consts.ENABLE_DEBUG, false, false,
- InputMethodManagerService.TAG);
+ InputMethodManagerService.TAG),
+ IME_VISIBILITY_APPLIER_DEBUG(Consts.ENABLE_DEBUG, false, false,
+ DefaultImeVisibilityApplier.TAG),
+ IME_VIS_STATE_COMPUTER_DEBUG(Consts.ENABLE_DEBUG, false, false,
+ ImeVisibilityStateComputer.TAG);
private final boolean mEnabled;
private volatile boolean mLogToProto;
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 5fe8318dbb3f..2c07a3179344 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString;
import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS;
+import static com.android.server.inputmethod.ImeProtoLogGroup.IME_VIS_STATE_COMPUTER_DEBUG;
import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget;
import android.accessibilityservice.AccessibilityService;
@@ -58,6 +59,7 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.protolog.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -72,9 +74,7 @@ import java.util.WeakHashMap;
*/
public final class ImeVisibilityStateComputer {
- private static final String TAG = "ImeVisibilityStateComputer";
-
- private static final boolean DEBUG = InputMethodManagerService.DEBUG;
+ static final String TAG = "ImeVisibilityStateComputer";
@UserIdInt
private final int mUserId;
@@ -292,12 +292,14 @@ public final class ImeVisibilityStateComputer {
@InputMethodManager.HideFlags int hideFlags) {
if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mRequestedShowExplicitly || mShowForced)) {
- if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
+ "Not hiding: explicit show not cancelled by non-explicit hide");
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
return false;
}
if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
- if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
+ "Not hiding: forced show not cancelled by not-always hide");
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return false;
}
@@ -417,8 +419,8 @@ public final class ImeVisibilityStateComputer {
@GuardedBy("ImfLock.class")
private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
- if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken
- + ", state=" + newState);
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "setWindowStateInner, windowToken=%s, state=%s",
+ windowToken, newState);
mRequestWindowStateMap.put(windowToken, newState);
}
@@ -441,7 +443,8 @@ public final class ImeVisibilityStateComputer {
}
@GuardedBy("ImfLock.class")
- ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
+ ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible,
+ boolean imeRequestedVisible) {
// TODO: Output the request IME visibility state according to the requested window state
final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
// Should we auto-show the IME even if the caller has not
@@ -466,7 +469,7 @@ public final class ImeVisibilityStateComputer {
// Because the app might leverage these flags to hide soft-keyboard with showing their own
// UI for input.
if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) {
- if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Will show input to restore visibility");
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
state.setRequestedImeVisible(true);
@@ -483,7 +486,8 @@ public final class ImeVisibilityStateComputer {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
// soft input window if it is shown.
- if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
+ "Unspecified window will hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
}
@@ -495,7 +499,7 @@ public final class ImeVisibilityStateComputer {
// them good context without input information being obscured
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
- if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Unspecified window will show input");
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
}
@@ -513,7 +517,8 @@ public final class ImeVisibilityStateComputer {
// the WindowState, as they're already in the correct state
break;
} else if (isForwardNavigation) {
- if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
+ "Window asks to hide input going forward");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
}
@@ -524,7 +529,7 @@ public final class ImeVisibilityStateComputer {
// the WindowState, as they're already in the correct state
break;
} else if (state.hasImeFocusChanged()) {
- if (DEBUG) Slog.v(TAG, "Window asks to hide input");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window asks to hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
}
@@ -532,7 +537,8 @@ public final class ImeVisibilityStateComputer {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
if (isForwardNavigation) {
if (allowVisible) {
- if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
+ "Window asks to show input going forward");
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
} else {
@@ -543,7 +549,7 @@ public final class ImeVisibilityStateComputer {
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
- if (DEBUG) Slog.v(TAG, "Window asks to always show input");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window asks to always show input");
if (allowVisible) {
if (state.hasImeFocusChanged()) {
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
@@ -565,12 +571,14 @@ public final class ImeVisibilityStateComputer {
// To maintain compatibility, we are now hiding the IME when we don't have
// an editor upon refocusing a window.
if (state.isStartInputByGainFocus()) {
- if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
+ "Same window without editor will hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
}
- if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus()
+ if (!state.hasEditorFocused() && (mInputShown || (Flags.refactorInsetsController()
+ && imeRequestedVisible)) && state.isStartInputByGainFocus()
&& mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
// Hide the soft-keyboard when the system do nothing for softInputModeState
// of the window being gained focus without an editor. This behavior benefits
@@ -579,7 +587,7 @@ public final class ImeVisibilityStateComputer {
// 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
// 2) SOFT_INPUT_STATE_VISIBLE state without an editor
// 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
- if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
+ ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window without editor will hide input");
if (Flags.refactorInsetsController()) {
state.setRequestedImeVisible(false);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index af726bd28718..23757757e336 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2902,12 +2902,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final long ident = Binder.clearCallingIdentity();
try {
if (windowPerceptible != null && !windowPerceptible) {
- if ((vis & InputMethodService.IME_VISIBLE) != 0) {
- vis &= ~InputMethodService.IME_VISIBLE;
- vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
- }
- } else {
- vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
+ vis &= ~InputMethodService.IME_VISIBLE;
}
final var curId = bindingController.getCurId();
// TODO(b/305849394): Make mMenuController multi-user aware.
@@ -3730,8 +3725,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod) {
// implemented by ZeroJankProxy
}
@@ -3744,7 +3739,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
@@ -3875,7 +3870,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs);
+ unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs,
+ imeRequestedVisible);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3904,7 +3900,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs,
+ boolean imeRequestedVisible) {
ProtoLog.v(IMMS_DEBUG, "startInputOrWindowGainedFocusInternalLocked: reason=%s"
+ " client=%s"
+ " inputContext=%s"
@@ -3915,12 +3912,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
+ " unverifiedTargetSdkVersion=%s"
+ " bindingController=%s"
+ " imeDispatcher=%s"
- + " cs=%s",
+ + " cs=%s"
+ + " imeRequestedVisible=%s",
InputMethodDebug.startInputReasonToString(startInputReason), client.asBinder(),
inputContext, editorInfo, InputMethodDebug.startInputFlagsToString(startInputFlags),
InputMethodDebug.softInputModeToString(softInputMode),
Integer.toHexString(windowFlags), unverifiedTargetSdkVersion, bindingController,
- imeDispatcher, cs);
+ imeDispatcher, cs, imeRequestedVisible);
final int userId = bindingController.getUserId();
final var userData = getUserData(userId);
@@ -3968,7 +3966,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
InputBindResult res = null;
final ImeVisibilityResult imeVisRes = visibilityStateComputer.computeState(windowState,
- isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags));
+ isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags),
+ imeRequestedVisible);
if (imeVisRes != null) {
boolean isShow = false;
switch (imeVisRes.getReason()) {
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 72529254545e..12c1d9cbb2a1 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -234,15 +234,15 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
- boolean useAsyncShowHideMethod) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
+ int startInputSeq, boolean useAsyncShowHideMethod) {
offload(() -> {
InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo,
inputConnection, remoteAccessibilityInputConnection,
unverifiedTargetSdkVersion,
- userId, imeDispatcher);
+ userId, imeDispatcher, imeRequestedVisible);
sendOnStartInputResult(client, result, startInputSeq);
// For first-time client bind, MSG_BIND should arrive after MSG_START_INPUT_RESULT.
if (result.result == InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION) {
@@ -269,7 +269,7 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
// Should never be called when flag is enabled i.e. when this proxy is used.
return null;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index f40d0dd18213..6db62c8397f3 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -16,6 +16,8 @@
package com.android.server.location.contexthub;
+import static com.android.server.location.contexthub.ContextHubTransactionManager.RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT;
+
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
@@ -44,6 +46,9 @@ import com.android.internal.annotations.GuardedBy;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -100,7 +105,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
private final Object mOpenSessionLock = new Object();
- static class SessionInfo {
+ static class Session {
enum SessionState {
/* The session is pending acceptance from the remote endpoint. */
PENDING,
@@ -119,7 +124,15 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
*/
private final Set<Integer> mPendingSequenceNumbers = new HashSet<>();
- SessionInfo(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) {
+ /**
+ * Stores the history of received messages that are timestamped. We use a LinkedHashMap to
+ * guarantee insertion ordering for easier manipulation of removing expired entries.
+ *
+ * <p>The key is the sequence number, and the value is the timestamp in milliseconds.
+ */
+ private final LinkedHashMap<Integer, Long> mRxMessageHistoryMap = new LinkedHashMap<>();
+
+ Session(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) {
mRemoteEndpointInfo = remoteEndpointInfo;
mRemoteInitiated = remoteInitiated;
}
@@ -157,11 +170,45 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
consumer.accept(sequenceNumber);
}
}
+
+ public boolean isInReliableMessageHistory(HubMessage message) {
+ if (!message.isResponseRequired()) return false;
+ // Clean up the history
+ Iterator<Map.Entry<Integer, Long>> iterator =
+ mRxMessageHistoryMap.entrySet().iterator();
+ long nowMillis = System.currentTimeMillis();
+ while (iterator.hasNext()) {
+ Map.Entry<Integer, Long> nextEntry = iterator.next();
+ long expiryMillis = RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT.toMillis();
+ if (nowMillis >= nextEntry.getValue() + expiryMillis) {
+ iterator.remove();
+ }
+ break;
+ }
+
+ return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber());
+ }
+
+ public void addReliableMessageToHistory(HubMessage message) {
+ if (!message.isResponseRequired()) return;
+ if (mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber())) {
+ long value = mRxMessageHistoryMap.get(message.getMessageSequenceNumber());
+ Log.w(
+ TAG,
+ "Message already exists in history (inserted @ "
+ + value
+ + " ms): "
+ + message);
+ return;
+ }
+ mRxMessageHistoryMap.put(
+ message.getMessageSequenceNumber(), System.currentTimeMillis());
+ }
}
/** A map between a session ID which maps to its current state. */
@GuardedBy("mOpenSessionLock")
- private final SparseArray<SessionInfo> mSessionInfoMap = new SparseArray<>();
+ private final SparseArray<Session> mSessionMap = new SparseArray<>();
/** The package name of the app that created the endpoint */
private final String mPackageName;
@@ -232,7 +279,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
synchronized (mOpenSessionLock) {
try {
- mSessionInfoMap.put(sessionId, new SessionInfo(destination, false));
+ mSessionMap.put(sessionId, new Session(destination, false));
mHubInterface.openEndpointSession(
sessionId, halEndpointInfo.id, mHalEndpointInfo.id, serviceDescriptor);
} catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
@@ -263,8 +310,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
super.unregister_enforcePermission();
synchronized (mOpenSessionLock) {
// Iterate in reverse since cleanupSessionResources will remove the entry
- for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
- int id = mSessionInfoMap.keyAt(i);
+ for (int i = mSessionMap.size() - 1; i >= 0; i--) {
+ int id = mSessionMap.keyAt(i);
halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
cleanupSessionResources(id);
}
@@ -290,14 +337,14 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
public void openSessionRequestComplete(int sessionId) {
super.openSessionRequestComplete_enforcePermission();
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info == null) {
throw new IllegalArgumentException(
"openSessionRequestComplete for invalid session id=" + sessionId);
}
try {
mHubInterface.endpointSessionOpenComplete(sessionId);
- info.setSessionState(SessionInfo.SessionState.ACTIVE);
+ info.setSessionState(Session.SessionState.ACTIVE);
} catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
}
@@ -310,7 +357,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
super.sendMessage_enforcePermission();
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info == null) {
throw new IllegalArgumentException(
"sendMessage for invalid session id=" + sessionId);
@@ -393,9 +440,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
} else {
synchronized (mOpenSessionLock) {
// Iterate in reverse since cleanupSessionResources will remove the entry
- for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
- int id = mSessionInfoMap.keyAt(i);
- HubEndpointInfo target = mSessionInfoMap.get(id).getRemoteEndpointInfo();
+ for (int i = mSessionMap.size() - 1; i >= 0; i--) {
+ int id = mSessionMap.keyAt(i);
+ HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
if (!hasEndpointPermissions(target)) {
halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED);
onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
@@ -415,13 +462,13 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
sb.append("wakelock: ").append(mWakeLock);
}
synchronized (mOpenSessionLock) {
- if (mSessionInfoMap.size() != 0) {
+ if (mSessionMap.size() != 0) {
sb.append(System.lineSeparator());
sb.append(" sessions: ");
sb.append(System.lineSeparator());
}
- for (int i = 0; i < mSessionInfoMap.size(); i++) {
- int id = mSessionInfoMap.keyAt(i);
+ for (int i = 0; i < mSessionMap.size(); i++) {
+ int id = mSessionMap.keyAt(i);
int count = i + 1;
sb.append(
" "
@@ -429,7 +476,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ ". id="
+ id
+ ", remote:"
- + mSessionInfoMap.get(id).getRemoteEndpointInfo());
+ + mSessionMap.get(id).getRemoteEndpointInfo());
sb.append(System.lineSeparator());
}
}
@@ -485,23 +532,23 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
Log.w(TAG, "Unknown session ID in onEndpointSessionOpenComplete: id=" + sessionId);
return;
}
- mSessionInfoMap.get(sessionId).setSessionState(SessionInfo.SessionState.ACTIVE);
+ mSessionMap.get(sessionId).setSessionState(Session.SessionState.ACTIVE);
}
invokeCallback((consumer) -> consumer.onSessionOpenComplete(sessionId));
}
/* package */ void onMessageReceived(int sessionId, HubMessage message) {
- byte code = onMessageReceivedInternal(sessionId, message);
- if (code != ErrorCode.OK && message.isResponseRequired()) {
- sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), code);
+ byte errorCode = onMessageReceivedInternal(sessionId, message);
+ if (errorCode != ErrorCode.OK && message.isResponseRequired()) {
+ sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode);
}
}
/* package */ void onMessageDeliveryStatusReceived(
int sessionId, int sequenceNumber, byte errorCode) {
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info == null || !info.isActive()) {
Log.w(TAG, "Received delivery status for invalid session: id=" + sessionId);
return;
@@ -517,7 +564,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/* package */ boolean hasSessionId(int sessionId) {
synchronized (mOpenSessionLock) {
- return mSessionInfoMap.contains(sessionId);
+ return mSessionMap.contains(sessionId);
}
}
@@ -531,8 +578,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
}
synchronized (mOpenSessionLock) {
- for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
- int id = mSessionInfoMap.keyAt(i);
+ for (int i = mSessionMap.size() - 1; i >= 0; i--) {
+ int id = mSessionMap.keyAt(i);
onCloseEndpointSession(id, Reason.HUB_RESET);
}
}
@@ -555,7 +602,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
return Optional.of(Reason.UNSPECIFIED);
}
- mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true));
+ mSessionMap.put(sessionId, new Session(initiator, true));
}
boolean success =
@@ -567,7 +614,6 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
- HubEndpointInfo remote;
synchronized (mOpenSessionLock) {
if (!isSessionActive(sessionId)) {
Log.e(
@@ -578,29 +624,36 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ message);
return ErrorCode.PERMANENT_ERROR;
}
- remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo();
- }
+ HubEndpointInfo remote = mSessionMap.get(sessionId).getRemoteEndpointInfo();
+ if (mSessionMap.get(sessionId).isInReliableMessageHistory(message)) {
+ Log.e(TAG, "Dropping duplicate message: " + message);
+ return ErrorCode.TRANSIENT_ERROR;
+ }
- try {
- Binder.withCleanCallingIdentity(
- () -> {
- if (!notePermissions(remote)) {
- throw new RuntimeException(
- "Dropping message from "
- + remote
- + ". "
- + mPackageName
- + " doesn't have permission");
- }
- });
- } catch (RuntimeException e) {
- Log.e(TAG, e.getMessage());
- return ErrorCode.PERMISSION_DENIED;
- }
+ try {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (!notePermissions(remote)) {
+ throw new RuntimeException(
+ "Dropping message from "
+ + remote
+ + ". "
+ + mPackageName
+ + " doesn't have permission");
+ }
+ });
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage());
+ return ErrorCode.PERMISSION_DENIED;
+ }
- boolean success =
- invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
- return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
+ boolean success =
+ invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
+ if (success) {
+ mSessionMap.get(sessionId).addReliableMessageToHistory(message);
+ }
+ return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
+ }
}
/**
@@ -634,7 +687,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
*/
private boolean cleanupSessionResources(int sessionId) {
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info != null) {
if (!info.isRemoteInitiated()) {
mEndpointManager.returnSessionId(sessionId);
@@ -644,7 +697,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mTransactionManager.onMessageDeliveryResponse(
sequenceNumber, /* success= */ false);
});
- mSessionInfoMap.remove(sessionId);
+ mSessionMap.remove(sessionId);
}
return info != null;
}
@@ -656,7 +709,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
*/
private boolean isSessionActive(int sessionId) {
synchronized (mOpenSessionLock) {
- return hasSessionId(sessionId) && mSessionInfoMap.get(sessionId).isActive();
+ return hasSessionId(sessionId) && mSessionMap.get(sessionId).isActive();
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 6a72cc7c7779..7d9f2c29f943 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -24,6 +24,7 @@ import android.hardware.location.GeofenceHardware;
import android.hardware.location.GeofenceHardwareImpl;
import android.location.FusedBatchOptions;
import android.location.GnssAntennaInfo;
+import android.location.GnssAssistance;
import android.location.GnssCapabilities;
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementRequest;
@@ -35,6 +36,8 @@ import android.location.IGnssStatusListener;
import android.location.IGpsGeofenceHardware;
import android.location.Location;
import android.location.LocationManager;
+import android.location.flags.Flags;
+import android.location.provider.IGnssAssistanceCallback;
import android.location.util.identity.CallerIdentity;
import android.os.BatteryStats;
import android.os.Binder;
@@ -47,12 +50,13 @@ import com.android.internal.app.IBatteryStats;
import com.android.server.FgThread;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.injector.Injector;
+import com.android.server.location.provider.proxy.ProxyGnssAssistanceProvider;
import java.io.FileDescriptor;
import java.util.List;
/** Manages Gnss providers and related Gnss functions for LocationManagerService. */
-public class GnssManagerService {
+public class GnssManagerService implements GnssNative.GnssAssistanceCallbacks {
public static final String TAG = "GnssManager";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
@@ -75,6 +79,8 @@ public class GnssManagerService {
private final GnssMetrics mGnssMetrics;
+ private @Nullable ProxyGnssAssistanceProvider mProxyGnssAssistanceProvider = null;
+
public GnssManagerService(Context context, Injector injector, GnssNative gnssNative) {
mContext = context.createAttributionContext(ATTRIBUTION_ID);
mGnssNative = gnssNative;
@@ -100,6 +106,16 @@ public class GnssManagerService {
/** Called when system is ready. */
public void onSystemReady() {
mGnssLocationProvider.onSystemReady();
+
+ if (Flags.gnssAssistanceInterfaceJni()) {
+ mProxyGnssAssistanceProvider =
+ ProxyGnssAssistanceProvider.createAndRegister(mContext);
+ if (mProxyGnssAssistanceProvider == null) {
+ Log.e(TAG, "no gnss assistance provider found");
+ } else {
+ mGnssNative.setGnssAssistanceCallbacks(this);
+ }
+ }
}
/** Retrieve the GnssLocationProvider. */
@@ -323,6 +339,29 @@ public class GnssManagerService {
}
}
+ @Override
+ public void onRequestGnssAssistanceInject() {
+ if (!Flags.gnssAssistanceInterfaceJni()) {
+ return;
+ }
+ if (mProxyGnssAssistanceProvider == null) {
+ Log.e(TAG, "ProxyGnssAssistanceProvider is null");
+ return;
+ }
+ mProxyGnssAssistanceProvider.request(new IGnssAssistanceCallback.Stub() {
+ @Override
+ public void onError() {
+ Log.e(TAG, "GnssAssistanceCallback.onError");
+ }
+
+ @Override
+ public void onResult(GnssAssistance gnssAssistance) {
+ Log.d(TAG, "GnssAssistanceCallback.onResult");
+ mGnssNative.injectGnssAssistance(gnssAssistance);
+ }
+ });
+ }
+
private class GnssCapabilitiesHalModule implements GnssNative.BaseCallbacks {
GnssCapabilitiesHalModule(GnssNative gnssNative) {
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index c79a21a7eea8..7b4c56334868 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -23,6 +23,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.GnssAntennaInfo;
+import android.location.GnssAssistance;
import android.location.GnssCapabilities;
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementsEvent;
@@ -30,6 +31,7 @@ import android.location.GnssNavigationMessage;
import android.location.GnssSignalType;
import android.location.GnssStatus;
import android.location.Location;
+import android.location.flags.Flags;
import android.os.Binder;
import android.os.Handler;
import android.os.SystemClock;
@@ -275,6 +277,12 @@ public class GnssNative {
void onRequestPsdsDownload(int psdsType);
}
+ /** Callbacks for HAL requesting GNSS assistance. */
+ public interface GnssAssistanceCallbacks {
+ /** On request GnssAssistance injection. */
+ void onRequestGnssAssistanceInject();
+ }
+
/** Callbacks for AGPS functionality. */
public interface AGpsCallbacks {
@@ -400,6 +408,7 @@ public class GnssNative {
private TimeCallbacks mTimeCallbacks;
private LocationRequestCallbacks mLocationRequestCallbacks;
private PsdsCallbacks mPsdsCallbacks;
+ private @Nullable GnssAssistanceCallbacks mGnssAssistanceCallbacks;
private AGpsCallbacks mAGpsCallbacks;
private NotificationCallbacks mNotificationCallbacks;
@@ -504,6 +513,16 @@ public class GnssNative {
mNotificationCallbacks = Objects.requireNonNull(callbacks);
}
+ /** Sets GnssAssistanceCallbacks. */
+ public void setGnssAssistanceCallbacks(GnssAssistanceCallbacks callbacks) {
+ if (!Flags.gnssAssistanceInterfaceJni()) {
+ return;
+ }
+ Preconditions.checkState(!mRegistered);
+ Preconditions.checkState(mGnssAssistanceCallbacks == null);
+ mGnssAssistanceCallbacks = Objects.requireNonNull(callbacks);
+ }
+
/**
* Registers with the HAL and allows callbacks to begin. Once registered with the native HAL,
* no more callbacks can be added or set. Must only be called once.
@@ -1053,6 +1072,17 @@ public class GnssNative {
mGnssHal.injectNiSuplMessageData(data, length, slotIndex);
}
+ /**
+ * Injects GNSS assistance data into the GNSS HAL.
+ */
+ public void injectGnssAssistance(GnssAssistance assistance) {
+ if (!Flags.gnssAssistanceInterfaceJni()) {
+ return;
+ }
+ Preconditions.checkState(mRegistered);
+ mGnssHal.injectGnssAssistance(assistance);
+ }
+
@NativeEntryPoint
void reportGnssServiceDied() {
// Not necessary to clear (and restore) binder identity since it runs on another thread.
@@ -1269,6 +1299,15 @@ public class GnssNative {
}
@NativeEntryPoint
+ void gnssAssistanceInjectRequest() {
+ if (!Flags.gnssAssistanceInterfaceJni() || mGnssAssistanceCallbacks == null) {
+ return;
+ }
+ Binder.withCleanCallingIdentity(
+ () -> mGnssAssistanceCallbacks.onRequestGnssAssistanceInject());
+ }
+
+ @NativeEntryPoint
void reportGeofenceTransition(int geofenceId, Location location, int transition,
long transitionTimestamp) {
Binder.withCleanCallingIdentity(
@@ -1569,6 +1608,10 @@ public class GnssNative {
protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
native_inject_ni_supl_message_data(data, length, slotIndex);
}
+
+ protected void injectGnssAssistance(GnssAssistance gnssAssistance) {
+ native_inject_gnss_assistance(gnssAssistance);
+ }
}
// basic APIs
@@ -1718,4 +1761,7 @@ public class GnssNative {
private static native boolean native_supports_psds();
private static native void native_inject_psds_data(byte[] data, int length, int psdsType);
+
+ // GNSS Assistance APIs
+ private static native void native_inject_gnss_assistance(GnssAssistance gnssAssistance);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a0e543300ce7..42d0a5c4757a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -3618,6 +3618,12 @@ public class LockSettingsService extends ILockSettings.Stub {
return;
}
+ UserInfo userInfo = mInjector.getUserManagerInternal().getUserInfo(userId);
+ if (userInfo != null && userInfo.isForTesting()) {
+ Slog.i(TAG, "Keeping escrow data for test-only user");
+ return;
+ }
+
// Disable escrow token permanently on all other device/user types.
Slogf.i(TAG, "Permanently disabling support for escrow tokens on user %d", userId);
mSpManager.destroyEscrowData(userId);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 23e9ac5008f7..96e453963741 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -983,6 +983,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
Objects.requireNonNull(providerInfo, "providerInfo must not be null");
for (MediaRoute2Info route : providerInfo.getRoutes()) {
+ if (Flags.enableMirroringInMediaRouter2()
+ && route.supportsRemoteRouting()
+ && route.supportsSystemMediaRouting()
+ && route.getDeduplicationIds().isEmpty()) {
+ // This code is not accessible if the app is using the public API.
+ throw new SecurityException("Route is missing deduplication id: " + route);
+ }
+
if (route.isSystemRoute()) {
throw new SecurityException(
"Only the system is allowed to publish system routes. "
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index debac9436bb3..988924d9f498 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -27,6 +27,19 @@ import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_RELEASE_SESSION;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_ROUTE_ID;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_MANAGER_RECORD_NOT_FOUND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_PERMISSION_DENIED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
import android.Manifest;
import android.annotation.NonNull;
@@ -51,6 +64,7 @@ import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.SuggestedDeviceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -78,6 +92,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -130,9 +145,14 @@ class MediaRouter2ServiceImpl {
private final ArrayMap<IBinder, RouterRecord> mAllRouterRecords = new ArrayMap<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
+
@GuardedBy("mLock")
private int mCurrentActiveUserId = -1;
+ @GuardedBy("mLock")
+ private static final MediaRouterMetricLogger mMediaRouterMetricLogger =
+ new MediaRouterMetricLogger();
+
private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
(uid, importance) -> {
synchronized (mLock) {
@@ -350,8 +370,8 @@ class MediaRouter2ServiceImpl {
}
}
- public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router,
- @NonNull RouteDiscoveryPreference preference) {
+ public void setDiscoveryRequestWithRouter2(
+ @NonNull IMediaRouter2 router, @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(router, "router must not be null");
Objects.requireNonNull(preference, "preference must not be null");
@@ -409,8 +429,8 @@ class MediaRouter2ServiceImpl {
}
}
- public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router,
- @NonNull MediaRoute2Info route, int volume) {
+ public void setRouteVolumeWithRouter2(
+ @NonNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(router, "router must not be null");
Objects.requireNonNull(route, "route must not be null");
@@ -439,12 +459,7 @@ class MediaRouter2ServiceImpl {
try {
synchronized (mLock) {
requestCreateSessionWithRouter2Locked(
- requestId,
- managerRequestId,
- router,
- oldSession,
- route,
- sessionHints);
+ requestId, managerRequestId, router, oldSession, route, sessionHints);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -541,6 +556,36 @@ class MediaRouter2ServiceImpl {
}
}
+ public void setDeviceSuggestionsWithRouter2(
+ @NonNull IMediaRouter2 router,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ Objects.requireNonNull(router, "router must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setDeviceSuggestionsWithRouter2Locked(router, suggestedDeviceInfo);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2(
+ @NonNull IMediaRouter2 router) {
+ Objects.requireNonNull(router, "router must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return getDeviceSuggestionsWithRouter2Locked(router);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
// End of methods that implement MediaRouter2 operations.
// Start of methods that implement MediaRouter2Manager operations.
@@ -795,6 +840,36 @@ class MediaRouter2ServiceImpl {
}
}
+ public void setDeviceSuggestionsWithManager(
+ @NonNull IMediaRouter2Manager manager,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ Objects.requireNonNull(manager, "manager must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setDeviceSuggestionsWithManagerLocked(manager, suggestedDeviceInfo);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager(
+ @NonNull IMediaRouter2Manager manager) {
+ Objects.requireNonNull(manager, "manager must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return getDeviceSuggestionsWithManagerLocked(manager);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public boolean showMediaOutputSwitcherWithProxyRouter(
@NonNull IMediaRouter2Manager proxyRouter) {
@@ -1326,6 +1401,9 @@ class MediaRouter2ServiceImpl {
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1344,15 +1422,22 @@ class MediaRouter2ServiceImpl {
if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
ManagerRecord manager = userHandler.findManagerWithId(toRequesterId(managerRequestId));
if (manager == null || manager.mLastSessionCreationRequest == null) {
- Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
- + "Ignoring unknown request.");
+ Slog.w(TAG, "requestCreateSessionWithRouter2Locked: Ignoring unknown request.");
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_MANAGER_RECORD_NOT_FOUND);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
- if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
- oldSession.getId())) {
- Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
- + "Ignoring unmatched routing session.");
+ if (!TextUtils.equals(
+ manager.mLastSessionCreationRequest.mOldSession.getId(), oldSession.getId())) {
+ Slog.w(
+ TAG,
+ "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unmatched routing session.");
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
@@ -1364,8 +1449,13 @@ class MediaRouter2ServiceImpl {
&& route.isSystemRoute()) {
route = manager.mLastSessionCreationRequest.mRoute;
} else {
- Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
- + "Ignoring unmatched route.");
+ Slog.w(
+ TAG,
+ "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unmatched route.");
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_ROUTE_ID);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
@@ -1376,14 +1466,19 @@ class MediaRouter2ServiceImpl {
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
- Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
- + route);
+ Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to" + route);
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_PERMISSION_DENIED);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
}
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION);
userHandler.sendMessage(
obtainMessage(
UserHandler::requestCreateSessionWithRouter2OnHandler,
@@ -1403,6 +1498,9 @@ class MediaRouter2ServiceImpl {
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1411,6 +1509,9 @@ class MediaRouter2ServiceImpl {
TextUtils.formatSimple(
"selectRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
+ mMediaRouterMetricLogger.logOperationTriggered(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
@@ -1425,6 +1526,9 @@ class MediaRouter2ServiceImpl {
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1433,6 +1537,9 @@ class MediaRouter2ServiceImpl {
TextUtils.formatSimple(
"deselectRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
+ mMediaRouterMetricLogger.logOperationTriggered(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
@@ -1450,6 +1557,9 @@ class MediaRouter2ServiceImpl {
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1458,6 +1568,9 @@ class MediaRouter2ServiceImpl {
TextUtils.formatSimple(
"transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
+ mMediaRouterMetricLogger.logOperationTriggered(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
UserHandler userHandler = routerRecord.mUserRecord.mHandler;
String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
@@ -1516,6 +1629,9 @@ class MediaRouter2ServiceImpl {
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_RELEASE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1531,6 +1647,61 @@ class MediaRouter2ServiceImpl {
DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));
}
+ @GuardedBy("mLock")
+ private void setDeviceSuggestionsWithRouter2Locked(
+ @NonNull IMediaRouter2 router,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ final IBinder binder = router.asBinder();
+ final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+ if (routerRecord == null) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Ignoring set device suggestion for unknown router: %s", router));
+ return;
+ }
+
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "setDeviceSuggestions | router: %d suggestion: %d",
+ routerRecord.mPackageName, suggestedDeviceInfo));
+
+ routerRecord.mUserRecord.updateDeviceSuggestionsLocked(
+ routerRecord.mPackageName, routerRecord.mPackageName, suggestedDeviceInfo);
+ routerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::notifyDeviceSuggestionsUpdatedOnHandler,
+ routerRecord.mUserRecord.mHandler,
+ routerRecord.mPackageName,
+ routerRecord.mPackageName,
+ suggestedDeviceInfo));
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2Locked(
+ @NonNull IMediaRouter2 router) {
+ final IBinder binder = router.asBinder();
+ final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+ if (routerRecord == null) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Attempted to get device suggestion for unknown router: %s", router));
+ return null;
+ }
+
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "getDeviceSuggestions | router: %d", routerRecord.mPackageName));
+
+ return routerRecord.mUserRecord.getDeviceSuggestionsLocked(routerRecord.mPackageName);
+ }
+
// End of locked methods that are used by MediaRouter2.
// Start of locked methods that are used by MediaRouter2Manager.
@@ -1794,6 +1965,9 @@ class MediaRouter2ServiceImpl {
.findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE);
+
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
managerRecord.mUserRecord.mHandler,
@@ -1820,6 +1994,10 @@ class MediaRouter2ServiceImpl {
.findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE);
+
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
managerRecord.mUserRecord.mHandler,
@@ -1851,6 +2029,10 @@ class MediaRouter2ServiceImpl {
.findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE);
+
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(
UserHandler::transferToRouteOnHandler,
@@ -1910,6 +2092,68 @@ class MediaRouter2ServiceImpl {
uniqueRequestId, routerRecord, uniqueSessionId));
}
+ @GuardedBy("mLock")
+ private void setDeviceSuggestionsWithManagerLocked(
+ @NonNull IMediaRouter2Manager manager,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord == null || managerRecord.mTargetPackageName == null) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Ignoring set device suggestion for unknown manager: %s", manager));
+ return;
+ }
+
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "setDeviceSuggestions | manager: %d, suggestingPackageName: %d suggestion:"
+ + " %d",
+ managerRecord.mManagerId,
+ managerRecord.mOwnerPackageName,
+ suggestedDeviceInfo));
+
+ managerRecord.mUserRecord.updateDeviceSuggestionsLocked(
+ managerRecord.mTargetPackageName,
+ managerRecord.mOwnerPackageName,
+ suggestedDeviceInfo);
+ managerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::notifyDeviceSuggestionsUpdatedOnHandler,
+ managerRecord.mUserRecord.mHandler,
+ managerRecord.mTargetPackageName,
+ managerRecord.mOwnerPackageName,
+ suggestedDeviceInfo));
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManagerLocked(
+ @NonNull IMediaRouter2Manager manager) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord == null || managerRecord.mTargetPackageName == null) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Attempted to get device suggestion for unknown manager: %s", manager));
+ return null;
+ }
+
+ Slog.i(
+ TAG,
+ TextUtils.formatSimple(
+ "getDeviceSuggestionsWithManagerLocked | manager: %d",
+ managerRecord.mManagerId));
+
+ return managerRecord.mUserRecord.getDeviceSuggestionsLocked(
+ managerRecord.mTargetPackageName);
+ }
+
// End of locked methods that are used by MediaRouter2Manager.
// Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
@@ -1985,6 +2229,11 @@ class MediaRouter2ServiceImpl {
//TODO: make records private for thread-safety
final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
+
+ // @GuardedBy("mLock")
+ private final Map<String, Map<String, List<SuggestedDeviceInfo>>> mDeviceSuggestions =
+ new HashMap<>();
+
RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
Set<String> mActivelyScanningPackages = Set.of();
final UserHandler mHandler;
@@ -2014,6 +2263,25 @@ class MediaRouter2ServiceImpl {
return null;
}
+ // @GuardedBy("mLock")
+ public void updateDeviceSuggestionsLocked(
+ String packageName,
+ String suggestingPackageName,
+ List<SuggestedDeviceInfo> deviceSuggestions) {
+ mDeviceSuggestions.putIfAbsent(
+ packageName, new HashMap<String, List<SuggestedDeviceInfo>>());
+ Map<String, List<SuggestedDeviceInfo>> suggestions =
+ mDeviceSuggestions.get(packageName);
+ suggestions.put(suggestingPackageName, deviceSuggestions);
+ }
+
+ // @GuardedBy("mLock")
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsLocked(
+ String packageName) {
+ return mDeviceSuggestions.get(packageName);
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println(prefix + "UserRecord");
@@ -2252,6 +2520,15 @@ class MediaRouter2ServiceImpl {
}
}
+ public void notifyDeviceSuggestionsUpdated(
+ String suggestingPackageName, List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ try {
+ mRouter.notifyDeviceSuggestionsUpdated(suggestingPackageName, suggestedDeviceInfo);
+ } catch (RemoteException ex) {
+ logRemoteException("notifyDeviceSuggestionsUpdated", ex);
+ }
+ }
+
/**
* Sends the corresponding router a {@link RoutingSessionInfo session} creation request,
* with the given {@link MediaRoute2Info} as the initial member.
@@ -2792,7 +3069,8 @@ class MediaRouter2ServiceImpl {
if (!addedRoutes.isEmpty()) {
// If routes were added, newInfo cannot be null.
- Slog.i(TAG,
+ Slog.i(
+ TAG,
toLoggingMessage(
/* source= */ "addProviderRoutes",
newInfo.getUniqueId(),
@@ -2954,7 +3232,7 @@ class MediaRouter2ServiceImpl {
private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
- "selecting")) {
+ "selecting", uniqueRequestId)) {
return;
}
@@ -2963,8 +3241,12 @@ class MediaRouter2ServiceImpl {
if (provider == null) {
return;
}
- provider.selectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
- route.getOriginalId());
+ provider.selectRoute(
+ uniqueRequestId, getOriginalId(uniqueSessionId), route.getOriginalId());
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
// routerRecord can be null if the session is system's or RCN.
@@ -2972,7 +3254,7 @@ class MediaRouter2ServiceImpl {
@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
- "deselecting")) {
+ "deselecting", uniqueRequestId)) {
return;
}
@@ -2982,8 +3264,12 @@ class MediaRouter2ServiceImpl {
return;
}
- provider.deselectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
- route.getOriginalId());
+ provider.deselectRoute(
+ uniqueRequestId, getOriginalId(uniqueSessionId), route.getOriginalId());
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
// routerRecord can be null if the session is system's or RCN.
@@ -2996,7 +3282,7 @@ class MediaRouter2ServiceImpl {
@NonNull MediaRoute2Info route,
@RoutingSessionInfo.TransferReason int transferReason) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
- "transferring to")) {
+ "transferring to", uniqueRequestId)) {
return;
}
@@ -3016,18 +3302,25 @@ class MediaRouter2ServiceImpl {
getOriginalId(uniqueSessionId),
route.getOriginalId(),
transferReason);
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
// routerRecord is null if and only if the session is created without the request, which
// includes the system's session and RCN cases.
private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
- @NonNull String description) {
+ @NonNull String description, long uniqueRequestId) {
final String providerId = route.getProviderId();
final MediaRoute2Provider provider = findProvider(providerId);
if (provider == null) {
Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
+ "given route=" + route);
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND);
return false;
}
@@ -3050,6 +3343,9 @@ class MediaRouter2ServiceImpl {
+ getPackageNameFromNullableRecord(matchingRecord)
+ " route="
+ route);
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return false;
}
@@ -3057,6 +3353,9 @@ class MediaRouter2ServiceImpl {
if (sessionId == null) {
Slog.w(TAG, "Failed to get original session id from unique session id. "
+ "uniqueSessionId=" + uniqueSessionId);
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID);
return false;
}
@@ -3168,6 +3467,10 @@ class MediaRouter2ServiceImpl {
}
matchingRequest.mRouterRecord.notifySessionCreated(
toOriginalRequestId(uniqueRequestId), sessionInfo);
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
/**
@@ -3255,10 +3558,14 @@ class MediaRouter2ServiceImpl {
// Currently, only manager records can get notified of failures.
// TODO(b/282936553): Notify regular routers of request failures.
+
+ // Log the request result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MediaRouterMetricLogger.convertResultFromReason(reason));
}
- private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
- long uniqueRequestId, int reason) {
+ private boolean handleSessionCreationRequestFailed(
+ @NonNull MediaRoute2Provider provider, long uniqueRequestId, int reason) {
// Check whether the failure is about creating a session
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -3385,8 +3692,8 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifySessionCreatedToManagers(long managerRequestId,
- @NonNull RoutingSessionInfo session) {
+ private void notifySessionCreatedToManagers(
+ long managerRequestId, @NonNull RoutingSessionInfo session) {
int requesterId = toRequesterId(managerRequestId);
int originalRequestId = toOriginalRequestId(managerRequestId);
@@ -3464,6 +3771,41 @@ class MediaRouter2ServiceImpl {
// need to update routers other than the one making the update.
}
+ private void notifyDeviceSuggestionsUpdatedOnHandler(
+ String routerPackageName,
+ String suggestingPackageName,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ MediaRouter2ServiceImpl service = mServiceRef.get();
+ if (service == null) {
+ return;
+ }
+ List<IMediaRouter2Manager> managers = new ArrayList<>();
+ synchronized (service.mLock) {
+ for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
+ if (TextUtils.equals(managerRecord.mTargetPackageName, routerPackageName)) {
+ managers.add(managerRecord.mManager);
+ }
+ }
+ for (IMediaRouter2Manager manager : managers) {
+ try {
+ manager.notifyDeviceSuggestionsUpdated(
+ routerPackageName, suggestingPackageName, suggestedDeviceInfo);
+ } catch (RemoteException ex) {
+ Slog.w(
+ TAG,
+ "Failed to notify suggesteion changed. Manager probably died.",
+ ex);
+ }
+ }
+ for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+ if (TextUtils.equals(routerRecord.mPackageName, routerPackageName)) {
+ routerRecord.notifyDeviceSuggestionsUpdated(
+ suggestingPackageName, suggestedDeviceInfo);
+ }
+ }
+ }
+ }
+
private void updateDiscoveryPreferenceOnHandler() {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
diff --git a/services/core/java/com/android/server/media/MediaRouterMetricLogger.java b/services/core/java/com/android/server/media/MediaRouterMetricLogger.java
new file mode 100644
index 000000000000..56d2a1b22254
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouterMetricLogger.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media;
+
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
+
+import android.annotation.NonNull;
+import android.media.MediaRoute2ProviderService;
+import android.util.Log;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Logs metrics for MediaRouter2.
+ *
+ * @hide
+ */
+final class MediaRouterMetricLogger {
+ private static final String TAG = "MediaRouterMetricLogger";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int REQUEST_INFO_CACHE_CAPACITY = 100;
+
+ /** LRU cache to store request info. */
+ private final RequestInfoCache mRequestInfoCache;
+
+ /** Constructor for {@link MediaRouterMetricLogger}. */
+ public MediaRouterMetricLogger() {
+ mRequestInfoCache = new RequestInfoCache(REQUEST_INFO_CACHE_CAPACITY);
+ }
+
+ /**
+ * Adds a new request info to the cache.
+ *
+ * @param uniqueRequestId The unique request id.
+ * @param eventType The event type.
+ */
+ public void addRequestInfo(long uniqueRequestId, int eventType) {
+ RequestInfo requestInfo = new RequestInfo(uniqueRequestId, eventType);
+ mRequestInfoCache.put(requestInfo.mUniqueRequestId, requestInfo);
+ }
+
+ /**
+ * Removes a request info from the cache.
+ *
+ * @param uniqueRequestId The unique request id.
+ */
+ public void removeRequestInfo(long uniqueRequestId) {
+ mRequestInfoCache.remove(uniqueRequestId);
+ }
+
+ /**
+ * Logs an operation failure.
+ *
+ * @param eventType The event type.
+ * @param result The result of the operation.
+ */
+ public void logOperationFailure(int eventType, int result) {
+ logMediaRouterEvent(eventType, result);
+ }
+
+ /**
+ * Logs an operation triggered.
+ *
+ * @param eventType The event type.
+ */
+ public void logOperationTriggered(int eventType, int result) {
+ logMediaRouterEvent(eventType, result);
+ }
+
+ /**
+ * Logs the result of a request.
+ *
+ * @param uniqueRequestId The unique request id.
+ * @param result The result of the request.
+ */
+ public void logRequestResult(long uniqueRequestId, int result) {
+ RequestInfo requestInfo = mRequestInfoCache.get(uniqueRequestId);
+ if (requestInfo == null) {
+ Slog.w(
+ TAG,
+ "logRequestResult: No RequestInfo found for uniqueRequestId="
+ + uniqueRequestId);
+ return;
+ }
+
+ int eventType = requestInfo.mEventType;
+ logMediaRouterEvent(eventType, result);
+
+ removeRequestInfo(uniqueRequestId);
+ }
+
+ /**
+ * Converts a reason code from {@link MediaRoute2ProviderService} to a result code for logging.
+ *
+ * @param reason The reason code from {@link MediaRoute2ProviderService}.
+ * @return The result code for logging.
+ */
+ public static int convertResultFromReason(int reason) {
+ switch (reason) {
+ case MediaRoute2ProviderService.REASON_UNKNOWN_ERROR:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR;
+ case MediaRoute2ProviderService.REASON_REJECTED:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+ case MediaRoute2ProviderService.REASON_NETWORK_ERROR:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR;
+ case MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE;
+ case MediaRoute2ProviderService.REASON_INVALID_COMMAND:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+ case MediaRoute2ProviderService.REASON_UNIMPLEMENTED:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED;
+ case MediaRoute2ProviderService.REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+ default:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Gets the size of the request info cache.
+ *
+ * @return The size of the request info cache.
+ */
+ @VisibleForTesting
+ public int getRequestCacheSize() {
+ return mRequestInfoCache.size();
+ }
+
+ private void logMediaRouterEvent(int eventType, int result) {
+ MediaRouterStatsLog.write(
+ MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED, eventType, result);
+
+ if (DEBUG) {
+ Slog.d(TAG, "logMediaRouterEvent: " + eventType + " " + result);
+ }
+ }
+
+ /** A cache for storing request info that evicts entries when it reaches its capacity. */
+ class RequestInfoCache extends LinkedHashMap<Long, RequestInfo> {
+
+ public final int capacity;
+
+ /**
+ * Constructor for {@link RequestInfoCache}.
+ *
+ * @param capacity The maximum capacity of the cache.
+ */
+ public RequestInfoCache(int capacity) {
+ super(capacity, 1.0f, true);
+ this.capacity = capacity;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<Long, RequestInfo> eldest) {
+ boolean shouldRemove = size() > capacity;
+ if (shouldRemove) {
+ Slog.d(TAG, "Evicted request info: " + eldest.getValue());
+ logOperationTriggered(
+ eldest.getValue().mEventType,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
+ }
+ return shouldRemove;
+ }
+ }
+
+ /** Class to store request info. */
+ static class RequestInfo {
+ public final long mUniqueRequestId;
+ public final int mEventType;
+
+ /**
+ * Constructor for {@link RequestInfo}.
+ *
+ * @param uniqueRequestId The unique request id.
+ * @param eventType The event type.
+ */
+ RequestInfo(long uniqueRequestId, int eventType) {
+ mUniqueRequestId = uniqueRequestId;
+ mEventType = eventType;
+ }
+
+ /**
+ * Dumps the request info.
+ *
+ * @param pw The print writer.
+ * @param prefix The prefix for the output.
+ */
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "RequestInfo");
+ String indent = prefix + " ";
+ pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
+ pw.println(indent + "mEventType=" + mEventType);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 35bb19943a24..11f449e790a8 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -49,6 +49,7 @@ import android.media.RemoteDisplayState.RemoteDisplayInfo;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.media.SuggestedDeviceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -80,6 +81,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -526,6 +528,21 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
+ public void setDeviceSuggestionsWithRouter2(
+ IMediaRouter2 router, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ mService2.setDeviceSuggestionsWithRouter2(router, suggestedDeviceInfo);
+ }
+
+ // Binder call
+ @Override
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2(
+ IMediaRouter2 router) {
+ return mService2.getDeviceSuggestionsWithRouter2(router);
+ }
+
+ // Binder call
+ @Override
public List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager) {
return mService2.getRemoteSessions(manager);
}
@@ -666,6 +683,22 @@ public final class MediaRouterService extends IMediaRouterService.Stub
return mService2.showMediaOutputSwitcherWithProxyRouter(proxyRouter);
}
+ // Binder call
+ @Override
+ public void setDeviceSuggestionsWithManager(
+ @NonNull IMediaRouter2Manager manager,
+ @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
+ mService2.setDeviceSuggestionsWithManager(manager, suggestedDeviceInfo);
+ }
+
+ // Binder call
+ @Override
+ @Nullable
+ public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager(
+ IMediaRouter2Manager manager) {
+ return mService2.getDeviceSuggestionsWithManager(manager);
+ }
+
void restoreBluetoothA2dp() {
try {
boolean a2dpOn;
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 91a2843ccaf7..ad108f64ffe3 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -28,6 +28,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.hardware.audio.effect.DefaultExtension;
import android.hardware.tv.mediaquality.AmbientBacklightColorFormat;
import android.hardware.tv.mediaquality.IMediaQuality;
import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener;
@@ -48,7 +49,6 @@ import android.media.quality.IMediaQualityManager;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
import android.media.quality.MediaQualityContract.BaseParameters;
-import android.media.quality.MediaQualityManager;
import android.media.quality.ParameterCapability;
import android.media.quality.PictureProfile;
import android.media.quality.PictureProfileHandle;
@@ -58,6 +58,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -187,7 +188,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) {
+ public PictureProfile createPictureProfile(PictureProfile pp, int userId) {
if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty()
&& !incomingPackageEqualsCallingUidPackage(pp.getPackageName()))
&& !hasGlobalPictureQualityServicePermission()) {
@@ -221,7 +222,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
+ public void updatePictureProfile(String id, PictureProfile pp, int userId) {
Long dbId = mPictureProfileTempIdMap.getKey(id);
if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
mMqManagerNotifier.notifyOnPictureProfileError(id,
@@ -249,7 +250,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public void removePictureProfile(String id, UserHandle user) {
+ public void removePictureProfile(String id, int userId) {
synchronized (mPictureProfileLock) {
Long dbId = mPictureProfileTempIdMap.getKey(id);
@@ -290,10 +291,8 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public PictureProfile getPictureProfile(int type, String name, Bundle options,
- UserHandle user) {
- boolean includeParams =
- options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
+ public PictureProfile getPictureProfile(int type, String name, boolean includeParams,
+ int userId) {
String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ BaseParameters.PARAMETER_NAME + " = ? AND "
+ BaseParameters.PARAMETER_PACKAGE + " = ?";
@@ -327,7 +326,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
public List<PictureProfile> getPictureProfilesByPackage(
- String packageName, Bundle options, UserHandle user) {
+ String packageName, boolean includeParams, int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(null,
PictureProfile.ERROR_NO_PERMISSION,
@@ -335,8 +334,6 @@ public class MediaQualityService extends SystemService {
}
synchronized (mPictureProfileLock) {
- boolean includeParams =
- options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
String[] selectionArguments = {packageName};
return mMqDatabaseUtils.getPictureProfilesBasedOnConditions(MediaQualityUtils
@@ -347,17 +344,17 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) {
+ public List<PictureProfile> getAvailablePictureProfiles(boolean includeParams, int userId) {
String packageName = getPackageOfCallingUid();
if (packageName != null) {
- return getPictureProfilesByPackage(packageName, options, user);
+ return getPictureProfilesByPackage(packageName, includeParams, userId);
}
return new ArrayList<>();
}
@GuardedBy("mPictureProfileLock")
@Override
- public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
+ public boolean setDefaultPictureProfile(String profileId, int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(profileId,
PictureProfile.ERROR_NO_PERMISSION,
@@ -370,13 +367,21 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
+ PictureParameters pp = new PictureParameters();
PictureParameter[] pictureParameters = MediaQualityUtils
.convertPersistableBundleToPictureParameterList(params);
- PictureParameters pp = new PictureParameters();
+ PersistableBundle vendorPictureParameters = params
+ .getPersistableBundle(BaseParameters.VENDOR_PARAMETERS);
+ Parcel parcel = Parcel.obtain();
+ if (vendorPictureParameters != null) {
+ setVendorPictureParameters(pp, parcel, vendorPictureParameters);
+ }
+
pp.pictureParameters = pictureParameters;
mMediaQuality.sendDefaultPictureParameters(pp);
+ parcel.recycle();
return true;
}
} catch (RemoteException e) {
@@ -387,7 +392,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public List<String> getPictureProfilePackageNames(UserHandle user) {
+ public List<String> getPictureProfilePackageNames(int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(null,
PictureProfile.ERROR_NO_PERMISSION,
@@ -406,7 +411,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public List<PictureProfileHandle> getPictureProfileHandle(String[] ids, UserHandle user) {
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] ids, int userId) {
List<PictureProfileHandle> toReturn = new ArrayList<>();
synchronized (mPictureProfileLock) {
for (String id : ids) {
@@ -423,13 +428,13 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public List<SoundProfileHandle> getSoundProfileHandle(String[] ids, UserHandle user) {
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] ids, int userId) {
List<SoundProfileHandle> toReturn = new ArrayList<>();
synchronized (mSoundProfileLock) {
for (String id : ids) {
Long key = mSoundProfileTempIdMap.getKey(id);
if (key != null) {
- toReturn.add(new SoundProfileHandle(key));
+ toReturn.add(MediaQualityUtils.SOUND_PROFILE_HANDLE_NONE);
} else {
toReturn.add(null);
}
@@ -440,7 +445,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) {
+ public SoundProfile createSoundProfile(SoundProfile sp, int userId) {
if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
&& !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
&& !hasGlobalPictureQualityServicePermission()) {
@@ -473,7 +478,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) {
+ public void updateSoundProfile(String id, SoundProfile sp, int userId) {
Long dbId = mSoundProfileTempIdMap.getKey(id);
if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
mMqManagerNotifier.notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
@@ -502,7 +507,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public void removeSoundProfile(String id, UserHandle user) {
+ public void removeSoundProfile(String id, int userId) {
synchronized (mSoundProfileLock) {
Long dbId = mSoundProfileTempIdMap.getKey(id);
SoundProfile toDelete = mMqDatabaseUtils.getSoundProfile(dbId);
@@ -542,10 +547,8 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public SoundProfile getSoundProfile(int type, String name, Bundle options,
- UserHandle user) {
- boolean includeParams =
- options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
+ public SoundProfile getSoundProfile(int type, String name, boolean includeParams,
+ int userId) {
String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ BaseParameters.PARAMETER_NAME + " = ? AND "
+ BaseParameters.PARAMETER_PACKAGE + " = ?";
@@ -579,15 +582,13 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
public List<SoundProfile> getSoundProfilesByPackage(
- String packageName, Bundle options, UserHandle user) {
+ String packageName, boolean includeParams, int userId) {
if (!hasGlobalSoundQualityServicePermission()) {
mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
}
synchronized (mSoundProfileLock) {
- boolean includeParams =
- options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
String[] selectionArguments = {packageName};
return mMqDatabaseUtils.getSoundProfilesBasedOnConditions(MediaQualityUtils
@@ -598,17 +599,17 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public List<SoundProfile> getAvailableSoundProfiles(Bundle options, UserHandle user) {
+ public List<SoundProfile> getAvailableSoundProfiles(boolean includeParams, int userId) {
String packageName = getPackageOfCallingUid();
if (packageName != null) {
- return getSoundProfilesByPackage(packageName, options, user);
+ return getSoundProfilesByPackage(packageName, includeParams, userId);
}
return new ArrayList<>();
}
@GuardedBy("mSoundProfileLock")
@Override
- public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
+ public boolean setDefaultSoundProfile(String profileId, int userId) {
if (!hasGlobalSoundQualityServicePermission()) {
mMqManagerNotifier.notifyOnSoundProfileError(profileId,
SoundProfile.ERROR_NO_PERMISSION,
@@ -638,7 +639,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public List<String> getSoundProfilePackageNames(UserHandle user) {
+ public List<String> getSoundProfilePackageNames(int userId) {
if (!hasGlobalSoundQualityServicePermission()) {
mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
@@ -737,7 +738,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mAmbientBacklightLock")
@Override
public void setAmbientBacklightSettings(
- AmbientBacklightSettings settings, UserHandle user) {
+ AmbientBacklightSettings settings, int userId) {
if (DEBUG) {
Slogf.d(TAG, "setAmbientBacklightSettings " + settings);
}
@@ -775,7 +776,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mAmbientBacklightLock")
@Override
- public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) {
+ public void setAmbientBacklightEnabled(boolean enabled, int userId) {
if (DEBUG) {
Slogf.d(TAG, "setAmbientBacklightEnabled " + enabled);
}
@@ -795,7 +796,7 @@ public class MediaQualityService extends SystemService {
@Override
public List<ParameterCapability> getParameterCapabilities(
- List<String> names, UserHandle user) {
+ List<String> names, int userId) {
byte[] byteArray = MediaQualityUtils.convertParameterToByteArray(names);
ParamCapability[] caps = new ParamCapability[byteArray.length];
try {
@@ -828,7 +829,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public List<String> getPictureProfileAllowList(UserHandle user) {
+ public List<String> getPictureProfileAllowList(int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(null,
PictureProfile.ERROR_NO_PERMISSION,
@@ -844,7 +845,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
+ public void setPictureProfileAllowList(List<String> packages, int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(null,
PictureProfile.ERROR_NO_PERMISSION,
@@ -857,7 +858,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public List<String> getSoundProfileAllowList(UserHandle user) {
+ public List<String> getSoundProfileAllowList(int userId) {
if (!hasGlobalSoundQualityServicePermission()) {
mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
@@ -872,7 +873,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
+ public void setSoundProfileAllowList(List<String> packages, int userId) {
if (!hasGlobalSoundQualityServicePermission()) {
mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
@@ -883,13 +884,13 @@ public class MediaQualityService extends SystemService {
}
@Override
- public boolean isSupported(UserHandle user) {
+ public boolean isSupported(int userId) {
return false;
}
@GuardedBy("mPictureProfileLock")
@Override
- public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
+ public void setAutoPictureQualityEnabled(boolean enabled, int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(null,
PictureProfile.ERROR_NO_PERMISSION,
@@ -910,7 +911,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public boolean isAutoPictureQualityEnabled(UserHandle user) {
+ public boolean isAutoPictureQualityEnabled(int userId) {
synchronized (mPictureProfileLock) {
try {
if (mMediaQuality != null) {
@@ -927,7 +928,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
+ public void setSuperResolutionEnabled(boolean enabled, int userId) {
if (!hasGlobalPictureQualityServicePermission()) {
mMqManagerNotifier.notifyOnPictureProfileError(null,
PictureProfile.ERROR_NO_PERMISSION,
@@ -948,7 +949,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
- public boolean isSuperResolutionEnabled(UserHandle user) {
+ public boolean isSuperResolutionEnabled(int userId) {
synchronized (mPictureProfileLock) {
try {
if (mMediaQuality != null) {
@@ -965,7 +966,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
+ public void setAutoSoundQualityEnabled(boolean enabled, int userId) {
if (!hasGlobalSoundQualityServicePermission()) {
mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
@@ -986,7 +987,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public boolean isAutoSoundQualityEnabled(UserHandle user) {
+ public boolean isAutoSoundQualityEnabled(int userId) {
synchronized (mSoundProfileLock) {
try {
if (mMediaQuality != null) {
@@ -1003,7 +1004,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mAmbientBacklightLock")
@Override
- public boolean isAmbientBacklightEnabled(UserHandle user) {
+ public boolean isAmbientBacklightEnabled(int userId) {
return false;
}
}
@@ -1428,11 +1429,19 @@ public class MediaQualityService extends SystemService {
MediaQualityUtils.convertPersistableBundleToPictureParameterList(
params);
+ PersistableBundle vendorPictureParameters = params
+ .getPersistableBundle(BaseParameters.VENDOR_PARAMETERS);
+ Parcel parcel = Parcel.obtain();
+ if (vendorPictureParameters != null) {
+ setVendorPictureParameters(pictureParameters, parcel, vendorPictureParameters);
+ }
+
android.hardware.tv.mediaquality.PictureProfile toReturn =
new android.hardware.tv.mediaquality.PictureProfile();
toReturn.pictureProfileId = id;
toReturn.parameters = pictureParameters;
+ parcel.recycle();
return toReturn;
}
@@ -1738,4 +1747,16 @@ public class MediaQualityService extends SystemService {
return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.VERSION;
}
}
+
+ private void setVendorPictureParameters(
+ PictureParameters pictureParameters,
+ Parcel parcel,
+ PersistableBundle vendorPictureParameters) {
+ vendorPictureParameters.writeToParcel(parcel, 0);
+ byte[] vendorBundleToByteArray = parcel.marshall();
+ DefaultExtension defaultExtension = new DefaultExtension();
+ defaultExtension.bytes = Arrays.copyOf(
+ vendorBundleToByteArray, vendorBundleToByteArray.length);
+ pictureParameters.vendorPictureParameters.setParcelable(defaultExtension);
+ }
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
index 88d3f1ff7c52..303c96750098 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -60,6 +60,11 @@ public final class MediaQualityUtils {
private static final String TAG = "MediaQualityUtils";
public static final String SETTINGS = "settings";
+ public static final SoundProfileHandle SOUND_PROFILE_HANDLE_NONE = new SoundProfileHandle();
+ static {
+ SOUND_PROFILE_HANDLE_NONE.id = -10000;
+ }
+
/**
* Convert PictureParameter List to PersistableBundle.
*/
@@ -1022,7 +1027,7 @@ public final class MediaQualityUtils {
getInputId(cursor),
getPackageName(cursor),
jsonToPersistableBundle(getSettingsString(cursor)),
- SoundProfileHandle.NONE
+ SOUND_PROFILE_HANDLE_NONE
);
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index dd52cce9e927..3f2c2228e453 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -46,7 +46,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
public class ConditionProviders extends ManagedServices {
@@ -203,14 +202,7 @@ public class ConditionProviders extends ManagedServices {
@Override
protected void loadDefaultsFromConfig() {
- for (String dndPackage : getDefaultDndAccessPackages(mContext)) {
- addDefaultComponentOrPackage(dndPackage);
- }
- }
-
- static List<String> getDefaultDndAccessPackages(Context context) {
- ArrayList<String> packages = new ArrayList<>();
- String defaultDndAccess = context.getResources().getString(
+ String defaultDndAccess = mContext.getResources().getString(
R.string.config_defaultDndAccessPackages);
if (defaultDndAccess != null) {
String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
@@ -218,10 +210,9 @@ public class ConditionProviders extends ManagedServices {
if (TextUtils.isEmpty(dnds[i])) {
continue;
}
- packages.add(dnds[i]);
+ addDefaultComponentOrPackage(dnds[i]);
}
}
- return packages;
}
@Override
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 6e5308e56aa8..3f4df1dcf4e9 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -1002,8 +1002,7 @@ public class GroupHelper {
private FullyQualifiedGroupKey getSectionGroupKeyWithFallback(final NotificationRecord record) {
final NotificationSectioner sectioner = getSection(record);
if (sectioner != null) {
- return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(),
- sectioner);
+ return FullyQualifiedGroupKey.forRecord(record, sectioner);
} else {
return getPreviousValidSectionKey(record);
}
@@ -1105,6 +1104,49 @@ public class GroupHelper {
}
}
+ /**
+ * Called when a group summary is posted. If there are any ungrouped notifications that are
+ * in that group, remove them as they are no longer candidates for autogrouping.
+ *
+ * @param summaryRecord the NotificationRecord for the newly posted group summary
+ * @param notificationList the full notification list from NotificationManagerService
+ */
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+ protected void onGroupSummaryAdded(final NotificationRecord summaryRecord,
+ final List<NotificationRecord> notificationList) {
+ String groupKey = summaryRecord.getSbn().getGroup();
+ synchronized (mAggregatedNotifications) {
+ final NotificationSectioner sectioner = getSection(summaryRecord);
+ if (sectioner == null) {
+ Slog.w(TAG, "onGroupSummaryAdded " + summaryRecord + ": no valid section found");
+ return;
+ }
+
+ FullyQualifiedGroupKey aggregateGroupKey = FullyQualifiedGroupKey.forRecord(
+ summaryRecord, sectioner);
+ ArrayMap<String, NotificationAttributes> ungrouped =
+ mUngroupedAbuseNotifications.getOrDefault(aggregateGroupKey,
+ new ArrayMap<>());
+ if (ungrouped.isEmpty()) {
+ // don't bother looking through the notification list if there are no pending
+ // ungrouped notifications in this section (likely to be the most common case)
+ return;
+ }
+
+ // Look through full notification list for any notifications belonging to this group;
+ // remove from ungrouped map if needed, as the presence of the summary means they will
+ // now be grouped
+ for (NotificationRecord r : notificationList) {
+ if (!r.getNotification().isGroupSummary()
+ && groupKey.equals(r.getSbn().getGroup())
+ && ungrouped.containsKey(r.getKey())) {
+ ungrouped.remove(r.getKey());
+ }
+ }
+ mUngroupedAbuseNotifications.put(aggregateGroupKey, ungrouped);
+ }
+ }
+
private record NotificationMoveOp(NotificationRecord record, FullyQualifiedGroupKey oldGroup,
FullyQualifiedGroupKey newGroup) { }
@@ -1496,8 +1538,8 @@ public class GroupHelper {
private boolean isNotificationAggregatedInSection(NotificationRecord record,
NotificationSectioner sectioner) {
- final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
- record.getUserId(), record.getSbn().getPackageName(), sectioner);
+ final FullyQualifiedGroupKey fullAggregateGroupKey = FullyQualifiedGroupKey.forRecord(
+ record, sectioner);
return record.getGroupKey().equals(fullAggregateGroupKey.toString());
}
@@ -1895,6 +1937,12 @@ public class GroupHelper {
this(userId, pkg, AGGREGATE_GROUP_KEY + (sectioner != null ? sectioner.mName : ""));
}
+ static FullyQualifiedGroupKey forRecord(NotificationRecord record,
+ @Nullable NotificationSectioner sectioner) {
+ return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(),
+ sectioner);
+ }
+
@Override
public String toString() {
return userId + "|" + pkg + "|" + "g:" + groupName;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 60371d751c4a..7a544cf1c26c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1075,6 +1075,7 @@ public class NotificationManagerService extends SystemService {
summary.getSbn().getNotification().getGroupAlertBehavior();
if (notificationForceGrouping()) {
+ summary.getNotification().flags |= Notification.FLAG_SILENT;
if (!summary.getChannel().getId().equals(summaryAttr.channelId)) {
NotificationChannel newChannel = mPreferencesHelper.getNotificationChannel(pkg,
summary.getUid(), summaryAttr.channelId, false);
@@ -2809,7 +2810,6 @@ public class NotificationManagerService extends SystemService {
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- mUgmInternal,
mShowReviewPermissionsNotification,
Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
@@ -7210,7 +7210,13 @@ public class NotificationManagerService extends SystemService {
final Uri originalSoundUri =
(originalChannel != null) ? originalChannel.getSound() : null;
if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) {
- PermissionHelper.grantUriPermission(mUgmInternal, soundUri, sourceUid);
+ Binder.withCleanCallingIdentity(() -> {
+ mUgmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(soundUri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(soundUri,
+ UserHandle.getUserId(sourceUid)));
+ });
}
}
@@ -7445,6 +7451,7 @@ public class NotificationManagerService extends SystemService {
// Override group key early for forced grouped notifications
r.setOverrideGroupKey(groupName);
}
+ r.getNotification().flags |= Notification.FLAG_SILENT;
}
addAutoGroupAdjustment(r, groupName);
@@ -10191,6 +10198,12 @@ public class NotificationManagerService extends SystemService {
}
if (isSummary) {
mSummaryByGroupKey.put(group, r);
+
+ if (notificationForceGrouping()) {
+ // If any formerly-ungrouped notifications will be grouped by this summary, update
+ // accordingly.
+ mGroupHelper.onGroupSummaryAdded(r, mNotificationList);
+ }
}
FlagChecker childrenFlagChecker = (flags) -> {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5a58f457ba08..cec5a93a2a15 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -37,7 +37,10 @@ import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.Person;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
@@ -46,6 +49,7 @@ import android.media.AudioAttributes;
import android.media.AudioSystem;
import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -813,7 +817,13 @@ public final class NotificationRecord {
}
if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())
&& signals.containsKey(KEY_SUMMARIZATION)) {
- mSummarization = signals.getString(KEY_SUMMARIZATION);
+ CharSequence summary = signals.getCharSequence(KEY_SUMMARIZATION,
+ signals.getString(KEY_SUMMARIZATION));
+ if (summary != null) {
+ mSummarization = summary.toString();
+ } else {
+ mSummarization = null;
+ }
EventLogTags.writeNotificationAdjusted(getKey(),
KEY_SUMMARIZATION, Boolean.toString(mSummarization != null));
}
@@ -1532,15 +1542,21 @@ public final class NotificationRecord {
* {@link #mGrantableUris}. Otherwise, this will either log or throw
* {@link SecurityException} depending on target SDK of enqueuing app.
*/
- private void visitGrantableUri(Uri uri, boolean userOverriddenUri,
- boolean isSound) {
+ private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
+ if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+
if (mGrantableUris != null && mGrantableUris.contains(uri)) {
return; // already verified this URI
}
final int sourceUid = getSbn().getUid();
+ final long ident = Binder.clearCallingIdentity();
try {
- PermissionHelper.grantUriPermission(mUgmInternal, uri, sourceUid);
+ // This will throw a SecurityException if the caller can't grant.
+ mUgmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
if (mGrantableUris == null) {
mGrantableUris = new ArraySet<>();
@@ -1560,6 +1576,8 @@ public final class NotificationRecord {
}
}
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 1464d481311a..b6f48890c528 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,25 +25,19 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.companion.virtual.VirtualDeviceManager;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
-import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
-import com.android.server.uri.UriGrantsManagerInternal;
import java.util.Collections;
import java.util.HashSet;
@@ -64,7 +58,7 @@ public final class PermissionHelper {
private final IPermissionManager mPermManager;
public PermissionHelper(Context context, IPackageManager packageManager,
- IPermissionManager permManager) {
+ IPermissionManager permManager) {
mContext = context;
mPackageManager = packageManager;
mPermManager = permManager;
@@ -304,19 +298,6 @@ public final class PermissionHelper {
return false;
}
- static void grantUriPermission(final UriGrantsManagerInternal ugmInternal, Uri uri,
- int sourceUid) {
- if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
- Binder.withCleanCallingIdentity(() -> {
- // This will throw a SecurityException if the caller can't grant.
- ugmInternal.checkGrantUriPermission(sourceUid, null,
- ContentProvider.getUriWithoutUserId(uri),
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
- });
- }
-
public static class PackagePermission {
public final String packageName;
public final @UserIdInt int userId;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 46ff7983bb2d..b26b4571b07a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -100,7 +100,6 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.PermissionHelper.PackagePermission;
-import com.android.server.uri.UriGrantsManagerInternal;
import org.json.JSONArray;
import org.json.JSONException;
@@ -228,7 +227,6 @@ public class PreferencesHelper implements RankingConfig {
private final NotificationChannelLogger mNotificationChannelLogger;
private final AppOpsManager mAppOps;
private final ManagedServices.UserProfiles mUserProfiles;
- private final UriGrantsManagerInternal mUgmInternal;
private SparseBooleanArray mBadgingEnabled;
private SparseBooleanArray mBubblesEnabled;
@@ -247,7 +245,6 @@ public class PreferencesHelper implements RankingConfig {
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- UriGrantsManagerInternal ugmInternal,
boolean showReviewPermissionsNotification, Clock clock) {
mContext = context;
mZenModeHelper = zenHelper;
@@ -258,7 +255,6 @@ public class PreferencesHelper implements RankingConfig {
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
mUserProfiles = userProfiles;
- mUgmInternal = ugmInternal;
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
@@ -1195,11 +1191,6 @@ public class PreferencesHelper implements RankingConfig {
}
clearLockedFieldsLocked(channel);
- // Verify that the app has permission to read the sound Uri
- // Only check for new channels, as regular apps can only set sound
- // before creating. See: {@link NotificationChannel#setSound}
- PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
-
channel.setImportanceLockedByCriticalDeviceFunction(
r.defaultAppLockedImportance || r.fixedImportance);
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
index dc7129cde5e5..ea7ee4addbe6 100644
--- a/services/core/java/com/android/server/notification/TEST_MAPPING
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -4,7 +4,10 @@
"name": "CtsNotificationTestCases_notification"
},
{
- "name": "FrameworksUiServicesTests_notification"
+ "name": "FrameworksUiServicesNotificationTests"
+ },
+ {
+ "name": "FrameworksUiServicesZenTests"
}
],
"postsubmit": [
diff --git a/services/core/java/com/android/server/notification/ZenConfigTrimmer.java b/services/core/java/com/android/server/notification/ZenConfigTrimmer.java
deleted file mode 100644
index d65954d11646..000000000000
--- a/services/core/java/com/android/server/notification/ZenConfigTrimmer.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 android.content.Context;
-import android.os.Parcel;
-import android.service.notification.SystemZenRules;
-import android.service.notification.ZenModeConfig;
-import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-class ZenConfigTrimmer {
-
- private static final String TAG = "ZenConfigTrimmer";
- private static final int MAXIMUM_PARCELED_SIZE = 150_000; // bytes
-
- private final HashSet<String> mTrustedPackages;
-
- ZenConfigTrimmer(Context context) {
- mTrustedPackages = new HashSet<>();
- mTrustedPackages.add(SystemZenRules.PACKAGE_ANDROID);
- mTrustedPackages.addAll(ConditionProviders.getDefaultDndAccessPackages(context));
- }
-
- void trimToMaximumSize(ZenModeConfig config) {
- Map<String, PackageRules> rulesPerPackage = new HashMap<>();
- for (ZenModeConfig.ZenRule rule : config.automaticRules.values()) {
- PackageRules pkgRules = rulesPerPackage.computeIfAbsent(rule.pkg, PackageRules::new);
- pkgRules.mRules.add(rule);
- }
-
- int totalSize = 0;
- for (PackageRules pkgRules : rulesPerPackage.values()) {
- totalSize += pkgRules.dataSize();
- }
-
- if (totalSize > MAXIMUM_PARCELED_SIZE) {
- List<PackageRules> deletionCandidates = new ArrayList<>();
- for (PackageRules pkgRules : rulesPerPackage.values()) {
- if (!mTrustedPackages.contains(pkgRules.mPkg)) {
- deletionCandidates.add(pkgRules);
- }
- }
- deletionCandidates.sort(Comparator.comparingInt(PackageRules::dataSize).reversed());
-
- evictPackagesFromConfig(config, deletionCandidates, totalSize);
- }
- }
-
- private static void evictPackagesFromConfig(ZenModeConfig config,
- List<PackageRules> deletionCandidates, int currentSize) {
- while (currentSize > MAXIMUM_PARCELED_SIZE && !deletionCandidates.isEmpty()) {
- PackageRules rulesToDelete = deletionCandidates.removeFirst();
- Slog.w(TAG, String.format("Evicting %s zen rules from package '%s' (%s bytes)",
- rulesToDelete.mRules.size(), rulesToDelete.mPkg, rulesToDelete.dataSize()));
-
- for (ZenModeConfig.ZenRule rule : rulesToDelete.mRules) {
- config.automaticRules.remove(rule.id);
- }
-
- currentSize -= rulesToDelete.dataSize();
- }
- }
-
- private static class PackageRules {
- private final String mPkg;
- private final List<ZenModeConfig.ZenRule> mRules;
- private int mParceledSize = -1;
-
- PackageRules(String pkg) {
- mPkg = pkg;
- mRules = new ArrayList<>();
- }
-
- private int dataSize() {
- if (mParceledSize >= 0) {
- return mParceledSize;
- }
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeParcelableList(mRules, 0);
- mParceledSize = parcel.dataSize();
- return mParceledSize;
- } finally {
- parcel.recycle();
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 8b09c2acb96a..889df512dd60 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -48,7 +48,6 @@ import static android.service.notification.ZenModeConfig.isImplicitRuleId;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving;
-import static com.android.server.notification.Flags.limitZenConfigSize;
import static java.util.Objects.requireNonNull;
@@ -193,7 +192,6 @@ public class ZenModeHelper {
private final ConditionProviders.Config mServiceConfig;
private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
private final ZenModeEventLogger mZenModeEventLogger;
- private final ZenConfigTrimmer mConfigTrimmer;
@VisibleForTesting protected int mZenMode;
@VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy;
@@ -228,7 +226,6 @@ public class ZenModeHelper {
mClock = clock;
addCallback(mMetrics);
mAppOps = context.getSystemService(AppOpsManager.class);
- mConfigTrimmer = new ZenConfigTrimmer(mContext);
mDefaultConfig = Flags.modesUi()
? ZenModeConfig.getDefaultConfig()
@@ -2064,20 +2061,20 @@ public class ZenModeHelper {
Log.w(TAG, "Invalid config in setConfigLocked; " + config);
return false;
}
- if (limitZenConfigSize() && (origin == ORIGIN_APP || origin == ORIGIN_USER_IN_APP)) {
- mConfigTrimmer.trimToMaximumSize(config);
- }
-
if (config.user != mUser) {
// simply store away for background users
- mConfigs.put(config.user, config);
+ synchronized (mConfigLock) {
+ mConfigs.put(config.user, config);
+ }
if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
return true;
}
// handle CPS backed conditions - danger! may modify config
mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
- mConfigs.put(config.user, config);
+ synchronized (mConfigLock) {
+ mConfigs.put(config.user, config);
+ }
if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
ZenLog.traceConfig(origin, reason, triggeringComponent, mConfig, config, callingUid);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 346d65a06cc9..76cd5c88b388 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -212,16 +212,6 @@ flag {
}
flag {
- name: "limit_zen_config_size"
- namespace: "systemui"
- description: "Enforce a maximum (serialized) size for the Zen configuration"
- bug: "387498139"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "managed_services_concurrent_multiuser"
namespace: "systemui"
description: "Enables ManagedServices to support Concurrent multi user environment"
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index ee29849465e2..737d943f084d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -308,7 +308,9 @@ final class OverlayManagerServiceImpl {
Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
}
// Update the state of all overlays that target this package.
- final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+ Set<UserPackage> targets = Collections.emptySet();
+ targets = CollectionUtils.addAll(targets,
+ updateOverlaysForTarget(pkgName, userId, 0 /* flags */));
// Remove all the overlays this package declares.
return CollectionUtils.addAll(targets,
diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
index 871d12ee6394..7791f5180e57 100644
--- a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
+++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
@@ -38,6 +38,7 @@ import com.android.server.SystemService;
import dalvik.system.VMDebug;
+import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -95,10 +96,16 @@ public class DynamicInstrumentationManagerService extends SystemService {
}
}
- Method method = MethodDescriptorParser.parseMethodDescriptor(
+ Executable executable = MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(), methodDescriptor);
- VMDebug.ExecutableMethodFileOffsets location =
- VMDebug.getExecutableMethodFileOffsets(method);
+ VMDebug.ExecutableMethodFileOffsets location;
+ if (com.android.art.flags.Flags.executableMethodFileOffsetsV2()) {
+ location = VMDebug.getExecutableMethodFileOffsets(executable);
+ } else if (executable instanceof Method) {
+ location = VMDebug.getExecutableMethodFileOffsets((Method) executable);
+ } else {
+ throw new UnsupportedOperationException();
+ }
try {
if (location == null) {
diff --git a/services/core/java/com/android/server/os/instrumentation/OWNERS b/services/core/java/com/android/server/os/instrumentation/OWNERS
new file mode 100644
index 000000000000..2522426d93f8
--- /dev/null
+++ b/services/core/java/com/android/server/os/instrumentation/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/UprobeStats:/OWNERS \ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 60d028b46970..f3797614dee0 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -392,7 +392,7 @@ public class BackgroundInstallControlService extends SystemService {
private boolean installedByAdb(String initiatingPackageName) {
// GTS tests needs to adopt shell identity to install apps.
- if(!SystemProperties.get("gts.transparency.bg-install-apps").isEmpty()) {
+ if(!SystemProperties.get("debug.gts.transparency.bg-install-apps").isEmpty()) {
Slog.d(TAG, "handlePackageAdd: is GTS tests, skipping ADB check");
} else if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
Slog.d(TAG, "handlePackageAdd: is installed by ADB, skipping");
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 734920435e26..3361dbc2df07 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -644,20 +644,6 @@ final class InstallRequest {
return mScanResult.mPkgSetting;
}
- @Nullable
- public PackageSetting getRealPackageSetting() {
- // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
- // setting needs to be passed to have a comparison, hide it behind an immutable
- // interface. There's no good reason to have 3 different ways to access the real
- // PackageSetting object, only one of which is actually correct.
- PackageSetting realPkgSetting = isExistingSettingCopied()
- ? getScanRequestPackageSetting() : getScannedPackageSetting();
- if (realPkgSetting == null) {
- realPkgSetting = getScannedPackageSetting();
- }
- return realPkgSetting;
- }
-
public boolean isExistingSettingCopied() {
assertScanResultExists();
return mScanResult.mExistingSettingCopied;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 635ef069741b..af788ea6ccdb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTA
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO;
import static android.content.pm.DataLoaderType.INCREMENTAL;
import static android.content.pm.DataLoaderType.STREAMING;
+import static android.content.pm.Flags.cloudCompilationVerification;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
@@ -3687,6 +3688,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
CollectionUtils.addAll(stagedSplitTypes, apk.getSplitTypes());
}
+ if (cloudCompilationVerification()) {
+ verifySdmSignatures(artManagedFilePaths, mSigningDetails);
+ }
+
if (removeSplitList.size() > 0) {
if (pkgInfo == null) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -4028,6 +4033,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
File targetArtManagedFile = new File(
ArtManagedInstallFileHelper.getTargetPathForApk(path, targetFile.getPath()));
stageFileLocked(artManagedFile, targetArtManagedFile);
+ if (!artManagedFile.equals(targetArtManagedFile)) {
+ // The file has been renamed. Update the list to reflect the change.
+ for (int i = 0; i < artManagedFilePaths.size(); ++i) {
+ if (artManagedFilePaths.get(i).equals(path)) {
+ artManagedFilePaths.set(i, targetArtManagedFile.getAbsolutePath());
+ }
+ }
+ }
}
}
@@ -4309,6 +4322,37 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
/**
+ * Verifies the signatures of SDM files.
+ *
+ * SDM is a file format that contains the cloud compilation artifacts. As a requirement, the SDM
+ * file should be signed with the same key as the APK.
+ *
+ * TODO(b/377474232): Move this logic to ART Service.
+ */
+ private static void verifySdmSignatures(List<String> artManagedFilePaths,
+ SigningDetails expectedSigningDetails) throws PackageManagerException {
+ ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ for (String path : artManagedFilePaths) {
+ if (!path.endsWith(".sdm")) {
+ continue;
+ }
+ // SDM is a format introduced in Android 16, so we don't need to support older
+ // signature schemes.
+ int minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3;
+ ParseResult<SigningDetails> verified =
+ ApkSignatureVerifier.verify(input, path, minSignatureScheme);
+ if (verified.isError()) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_INVALID_APK, "Failed to verify SDM signatures");
+ }
+ if (!expectedSigningDetails.signaturesMatchExactly(verified.getResult())) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_INVALID_APK, "SDM signatures are inconsistent with APK");
+ }
+ }
+ }
+
+ /**
* @return the uid of the owner this session
*/
public int getInstallerUid() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index cf598e89c988..62264dd73795 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -96,6 +96,7 @@ import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -1564,6 +1565,12 @@ class PackageManagerShellCommand extends ShellCommand {
private int doRunInstall(final InstallParams params) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
+ // Do not allow app installation if boot has not completed already
+ if (!SystemProperties.getBoolean("sys.boot_completed", false)) {
+ pw.println("Error: device is still booting.");
+ return 1;
+ }
+
int requestUserId = params.userId;
if (requestUserId != UserHandle.USER_ALL && requestUserId != UserHandle.USER_CURRENT) {
UserManagerInternal umi =
@@ -2174,6 +2181,13 @@ class PackageManagerShellCommand extends ShellCommand {
private int runUninstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
+
+ // Do not allow app uninstallation if boot has not completed already
+ if (!SystemProperties.getBoolean("sys.boot_completed", false)) {
+ pw.println("Error: device is still booting.");
+ return 1;
+ }
+
int flags = 0;
int userId = UserHandle.USER_ALL;
long versionCode = PackageManager.VERSION_CODE_HIGHEST;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index d3513053caf3..66e9e772e063 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1792,7 +1792,7 @@ public class ShortcutService extends IShortcutService.Stub {
void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) {
Objects.requireNonNull(token);
Objects.requireNonNull(r);
- synchronized (mServiceLock) {
+ synchronized (mHandler) {
mHandler.removeCallbacksAndMessages(token);
mHandler.postDelayed(r, token, CALLBACK_DELAY);
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 1fda4782fc86..92e8eb9cd1bb 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -133,6 +133,38 @@
]
},
{
+ "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJMultiUsersTestCases",
"file_patterns": [
"core/java/.*Install.*",
@@ -288,6 +320,38 @@
]
},
{
+ "name": "CtsPackageInstallerCUJInstallationViaIntentForResultTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "CtsPackageInstallerCUJInstallationViaSessionTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJMultiUsersTestCases",
"file_patterns": [
"core/java/.*Install.*",
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 93837b34f7b3..092ec8ef4a8a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7490,6 +7490,7 @@ public class UserManagerService extends IUserManager.Stub {
final long now = System.currentTimeMillis();
final long nowRealtime = SystemClock.elapsedRealtime();
final StringBuilder sb = new StringBuilder();
+ final Resources resources = Resources.getSystem();
if (args != null && args.length > 0) {
switch (args[0]) {
@@ -7570,10 +7571,15 @@ public class UserManagerService extends IUserManager.Stub {
// Dump some capabilities
pw.println();
- pw.print(" Max users: " + UserManager.getMaxSupportedUsers());
+ int effectiveMaxSupportedUsers = UserManager.getMaxSupportedUsers();
+ pw.print(" Max users: " + effectiveMaxSupportedUsers);
+ int defaultMaxSupportedUsers = resources.getInteger(R.integer.config_multiuserMaximumUsers);
+ if (effectiveMaxSupportedUsers != defaultMaxSupportedUsers) {
+ pw.print(" (built-in value: " + defaultMaxSupportedUsers + ")");
+ }
pw.println(" (limit reached: " + isUserLimitReached() + ")");
pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers());
- pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean(
+ pw.println(" All guests ephemeral: " + resources.getBoolean(
com.android.internal.R.bool.config_guestUserEphemeral));
pw.println(" Force ephemeral users: " + mForceEphemeralUsers);
final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
@@ -7588,7 +7594,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
if (isHeadlessSystemUserMode) {
- pw.println(" Can switch to headless system user: " + Resources.getSystem()
+ pw.println(" Can switch to headless system user: " + resources
.getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser));
}
pw.println(" User version: " + mUserVersion);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 58c5b1c90a66..5798aa919d96 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -36,6 +36,7 @@ import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_COMMUNAL;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING;
import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
@@ -111,6 +112,7 @@ public final class UserTypeFactory {
builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
builders.put(USER_TYPE_PROFILE_COMMUNAL, getDefaultTypeProfileCommunal());
builders.put(USER_TYPE_PROFILE_PRIVATE, getDefaultTypeProfilePrivate());
+ builders.put(USER_TYPE_PROFILE_SUPERVISING, getDefaultTypeProfileSupervising());
if (Build.IS_DEBUGGABLE) {
builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
}
@@ -343,6 +345,29 @@ public final class UserTypeFactory {
}
/**
+ * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_SUPERVISING}
+ * configuration.
+ */
+ private static UserTypeDetails.Builder getDefaultTypeProfileSupervising() {
+ return new UserTypeDetails.Builder()
+ .setName(USER_TYPE_PROFILE_SUPERVISING)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowed(1)
+ .setProfileParentRequired(false)
+ .setEnabled(android.multiuser.Flags.allowSupervisingProfile() ? 1 : 0)
+ .setLabels(R.string.profile_label_supervising)
+ .setDefaultRestrictions(getDefaultSupervisingProfileRestrictions())
+ .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(false)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_NO)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_NO)
+ .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+ .setCredentialShareableWithParent(false)
+ .setAlwaysVisible(true));
+ }
+
+ /**
* Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY}
* configuration.
*/
@@ -449,6 +474,12 @@ public final class UserTypeFactory {
return restrictions;
}
+ private static Bundle getDefaultSupervisingProfileRestrictions() {
+ final Bundle restrictions = getDefaultProfileRestrictions();
+ restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
+ return restrictions;
+ }
+
private static Bundle getDefaultManagedProfileSecureSettings() {
// Only add String values to the bundle, settings are written as Strings eventually
final Bundle settings = new Bundle();
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6ab30595e46b..098f113dc11a 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -897,7 +897,7 @@ final class DefaultPermissionGrantPolicy {
SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId);
grantPermissionsToSystemPackage(pm, voiceSearchPackage,
userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS,
- COARSE_BACKGROUND_LOCATION_PERMISSIONS);
+ COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS);
revokeRuntimePermissions(pm, voiceSearchPackage,
FINE_LOCATION_PERMISSIONS, false, userId);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f27194a7b792..3230e891db55 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3801,7 +3801,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
}
- // fall through
+ break;
case KeyEvent.KEYCODE_ESCAPE:
if (firstDown && event.isMetaPressed()) {
notifyKeyGestureCompleted(event,
@@ -4240,66 +4240,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (!useKeyGestureEventHandler()) {
return;
}
- mInputManager.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
- @Override
- public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
- @Nullable IBinder focusedToken) {
- boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
- focusedToken);
- if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch(
- (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
- mPowerKeyHandled = true;
- }
- return handled;
- }
-
- @Override
- public boolean isKeyGestureSupported(int gestureType) {
- switch (gestureType) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_HOME:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_BACK:
- case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
- case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE:
- case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
- case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP:
- case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
- case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
- case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
- case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB:
- case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
- case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
- case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
- return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
- return mAccessibilityShortcutController.isAccessibilityShortcutAvailable(
- isKeyguardLocked());
- case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
- return mAccessibilityShortcutController.isAccessibilityShortcutAvailable(
- false);
- default:
- return false;
- }
+ mInputManager.registerKeyGestureEventHandler((event, focusedToken) -> {
+ boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
+ focusedToken);
+ if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch(
+ (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
+ mPowerKeyHandled = true;
}
+ return handled;
});
}
@@ -4457,13 +4405,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
cancelPendingScreenshotChordAction();
}
return true;
- case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
- if (start) {
- interceptAccessibilityShortcutChord();
- } else {
- cancelPendingAccessibilityShortcutAction();
- }
- return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
if (start) {
interceptRingerToggleChord();
@@ -4481,14 +4422,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
cancelGlobalActionsAction();
}
return true;
- // TODO (b/358569822): Consolidate TV and non-TV gestures into same KeyGestureEvent
- case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
- if (start) {
- interceptAccessibilityGestureTv();
- } else {
- cancelAccessibilityGestureTv();
- }
- return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
if (start) {
interceptBugreportGestureTv();
@@ -6706,12 +6639,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return mKeyguardDelegate.isInputRestricted();
}
- /** {@inheritDoc} */
- @Override
- public boolean isKeyguardUnoccluding() {
- return keyguardOn() && !mWindowManagerFuncs.isAppTransitionStateIdle();
- }
-
@Override
public void dismissKeyguardLw(IKeyguardDismissCallback callback, CharSequence message) {
if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index cc31bb17dc9d..d7de22edf8ce 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -337,12 +337,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
void moveDisplayToTopIfAllowed(int displayId);
/**
- * Return whether the app transition state is idle.
- * @return {@code true} if app transition state is idle on the default display.
- */
- boolean isAppTransitionStateIdle();
-
- /**
* Enables the screen if all conditions are met.
*/
void enableScreenIfNeeded();
@@ -989,14 +983,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
public boolean isKeyguardOccluded();
/**
- * Return whether the keyguard is unoccluding.
- * @return {@code true} if the keyguard is unoccluding.
- */
- default boolean isKeyguardUnoccluding() {
- return false;
- }
-
- /**
* @return true if in keyguard is on.
*/
boolean isKeyguardShowing();
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 102dc071c7b6..417b5c7ae62b 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -16,6 +16,7 @@
package com.android.server.power;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -36,8 +37,8 @@ import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IWakeLockCallback;
import android.os.IScreenTimeoutPolicyListener;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -67,6 +68,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
@@ -147,7 +149,8 @@ public class Notifier {
@Nullable private final StatusBarManagerInternal mStatusBarManagerInternal;
private final TrustManager mTrustManager;
private final Vibrator mVibrator;
- private final WakeLockLog mWakeLockLog;
+ @NonNull private final WakeLockLog mPartialWakeLockLog;
+ @NonNull private final WakeLockLog mFullWakeLockLog;
private final DisplayManagerInternal mDisplayManagerInternal;
private final NotifierHandler mHandler;
@@ -250,7 +253,9 @@ public class Notifier {
mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
- mWakeLockLog = mInjector.getWakeLockLog(context);
+ mFullWakeLockLog = mInjector.getWakeLockLog(context);
+ mPartialWakeLockLog = mInjector.getWakeLockLog(context);
+
// Initialize interactive state for battery stats.
try {
mBatteryStats.noteInteractive(true);
@@ -324,7 +329,8 @@ public class Notifier {
// Ignore
}
}
- mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1);
+ getWakeLockLog(flags).onWakeLockAcquired(tag,
+ getUidForWakeLockLog(ownerUid, workSource), flags, /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockAcquired(flags);
}
@@ -473,7 +479,8 @@ public class Notifier {
// Ignore
}
}
- mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1);
+ getWakeLockLog(flags).onWakeLockReleased(tag,
+ getUidForWakeLockLog(ownerUid, workSource), /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason);
}
@@ -960,11 +967,18 @@ public class Notifier {
* @param pw The stream to print to.
*/
public void dump(PrintWriter pw) {
- if (mWakeLockLog != null) {
- mWakeLockLog.dump(pw);
- }
+ pw.println("Notifier:");
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("Partial Wakelock Log:");
+ mPartialWakeLockLog.dump(ipw);
+
+ ipw.println("");
+ ipw.println("Full Wakelock Log:");
+ mFullWakeLockLog.dump(ipw);
- mWakefulnessSessionObserver.dump(pw);
+ ipw.println("");
+ mWakefulnessSessionObserver.dump(ipw);
}
private void updatePendingBroadcastLocked() {
@@ -1232,7 +1246,9 @@ public class Notifier {
// Do Nothing
}
}
- mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
+
+ getWakeLockLog(flags).onWakeLockAcquired(tag, getUidForWakeLockLog(ownerUid, workSource),
+ flags, currentTime);
}
@SuppressLint("AndroidFrameworkRequiresPermission")
@@ -1253,7 +1269,8 @@ public class Notifier {
// Ignore
}
}
- mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
+ getWakeLockLog(flags).onWakeLockReleased(tag, getUidForWakeLockLog(ownerUid, workSource),
+ currentTime);
}
@SuppressLint("AndroidFrameworkRequiresPermission")
@@ -1419,6 +1436,15 @@ public class Notifier {
}
}
+ private @NonNull WakeLockLog getWakeLockLog(int flags) {
+ return PowerManagerService.isScreenLock(flags) ? mFullWakeLockLog : mPartialWakeLockLog;
+ }
+
+ private int getUidForWakeLockLog(int ownerUid, WorkSource workSource) {
+ int attributionUid = workSource != null ? workSource.getAttributionUid() : -1;
+ return attributionUid != -1 ? attributionUid : ownerUid;
+ }
+
private final class NotifierHandler extends Handler {
public NotifierHandler(Looper looper) {
@@ -1501,7 +1527,7 @@ public class Notifier {
/**
* Gets the WakeLockLog object
*/
- WakeLockLog getWakeLockLog(Context context);
+ @NonNull WakeLockLog getWakeLockLog(Context context);
/**
* Gets the AppOpsManager system service
@@ -1522,7 +1548,7 @@ public class Notifier {
}
@Override
- public WakeLockLog getWakeLockLog(Context context) {
+ public @NonNull WakeLockLog getWakeLockLog(Context context) {
return new WakeLockLog(context);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dd454cd61b2c..3eac4b54cd2b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1723,9 +1723,16 @@ public final class PowerManagerService extends SystemService
}
}
- @SuppressWarnings("deprecation")
private static boolean isScreenLock(final WakeLock wakeLock) {
- switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+ return isScreenLock(wakeLock.mFlags);
+ }
+
+ /**
+ * Returns if a wakelock flag corresponds to a screen wake lock.
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean isScreenLock(int flags) {
+ switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.FULL_WAKE_LOCK:
case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
case PowerManager.SCREEN_DIM_WAKE_LOCK:
diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java
index c4929c210e2c..b376417061db 100644
--- a/services/core/java/com/android/server/power/ScreenUndimDetector.java
+++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java
@@ -30,11 +30,11 @@ import android.provider.DeviceConfig;
import android.util.Slog;
import android.view.Display;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
/**
* Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen
@@ -48,7 +48,6 @@ public class ScreenUndimDetector {
/** DeviceConfig flag: is keep screen on feature enabled. */
static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled";
- private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true;
private static final int OUTCOME_POWER_BUTTON =
FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON;
private static final int OUTCOME_TIMEOUT =
@@ -58,15 +57,11 @@ public class ScreenUndimDetector {
/** DeviceConfig flag: how long should we keep the screen on. */
@VisibleForTesting
static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis";
- @VisibleForTesting
- static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10);
private long mKeepScreenOnForMillis;
/** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */
@VisibleForTesting
static final String KEY_UNDIMS_REQUIRED = "undims_required";
- @VisibleForTesting
- static final int DEFAULT_UNDIMS_REQUIRED = 2;
private int mUndimsRequired;
/**
@@ -76,8 +71,6 @@ public class ScreenUndimDetector {
@VisibleForTesting
static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
"max_duration_between_undims_millis";
- @VisibleForTesting
- static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5);
private long mMaxDurationBetweenUndimsMillis;
@VisibleForTesting
@@ -92,6 +85,7 @@ public class ScreenUndimDetector {
private long mUndimOccurredTime = -1;
private long mInteractionAfterUndimTime = -1;
private InternalClock mClock;
+ private Context mContext;
public ScreenUndimDetector() {
mClock = new InternalClock();
@@ -109,12 +103,13 @@ public class ScreenUndimDetector {
/** Should be called in parent's systemReady() */
public void systemReady(Context context) {
+ mContext = context;
readValuesFromDeviceConfig();
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
- context.getMainExecutor(),
+ mContext.getMainExecutor(),
(properties) -> onDeviceConfigChange(properties.getKeyset()));
- final PowerManager powerManager = context.getSystemService(PowerManager.class);
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE,
UNDIM_DETECTOR_WAKE_LOCK);
@@ -203,36 +198,44 @@ public class ScreenUndimDetector {
}
}
- private boolean readKeepScreenOnNotificationEnabled() {
+ private boolean readKeepScreenOnEnabled() {
+ boolean defaultKeepScreenOnEnabled = mContext.getResources().getBoolean(
+ R.bool.config_defaultPreventScreenTimeoutEnabled);
return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_KEEP_SCREEN_ON_ENABLED,
- DEFAULT_KEEP_SCREEN_ON_ENABLED);
+ defaultKeepScreenOnEnabled);
}
private long readKeepScreenOnForMillis() {
+ long defaultKeepScreenOnDuration = mContext.getResources().getInteger(
+ R.integer.config_defaultPreventScreenTimeoutForMillis);
return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_KEEP_SCREEN_ON_FOR_MILLIS,
- DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS);
+ defaultKeepScreenOnDuration);
}
private int readUndimsRequired() {
+ int defaultUndimsRequired = mContext.getResources().getInteger(
+ R.integer.config_defaultUndimsRequired);
int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_UNDIMS_REQUIRED,
- DEFAULT_UNDIMS_REQUIRED);
+ defaultUndimsRequired);
if (undimsRequired < 1 || undimsRequired > 5) {
Slog.e(TAG, "Provided undimsRequired=" + undimsRequired
- + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED);
- return DEFAULT_UNDIMS_REQUIRED;
+ + " is not allowed [1, 5]; using the default=" + defaultUndimsRequired);
+ return defaultUndimsRequired;
}
return undimsRequired;
}
private long readMaxDurationBetweenUndimsMillis() {
+ long defaultMaxDurationBetweenUndimsMillis = mContext.getResources().getInteger(
+ R.integer.config_defaultMaxDurationBetweenUndimsMillis);
return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
- DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS);
+ defaultMaxDurationBetweenUndimsMillis);
}
private void onDeviceConfigChange(@NonNull Set<String> keys) {
@@ -253,15 +256,16 @@ public class ScreenUndimDetector {
@VisibleForTesting
void readValuesFromDeviceConfig() {
- mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled();
+ mKeepScreenOnEnabled = readKeepScreenOnEnabled();
mKeepScreenOnForMillis = readKeepScreenOnForMillis();
mUndimsRequired = readUndimsRequired();
mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis();
Slog.i(TAG, "readValuesFromDeviceConfig():"
+ "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis
- + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled
- + "\nmUndimsRequired=" + mUndimsRequired);
+ + "\nmKeepScreenOnEnabled=" + mKeepScreenOnEnabled
+ + "\nmUndimsRequired=" + mUndimsRequired
+ + "\nmMaxDurationBetweenUndimsMillis=" + mMaxDurationBetweenUndimsMillis);
}
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index eda222e71c9e..7f152d6fc9fa 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -81,11 +81,12 @@ final class WakeLockLog {
private static final int TYPE_ACQUIRE = 0x1;
private static final int TYPE_RELEASE = 0x2;
private static final int MAX_LOG_ENTRY_BYTE_SIZE = 9;
- private static final int LOG_SIZE = 1024 * 10;
+ private static final int LOG_SIZE = 1024 * 3;
private static final int LOG_SIZE_MIN = MAX_LOG_ENTRY_BYTE_SIZE + 1;
- private static final int TAG_DATABASE_SIZE = 128;
+ private static final int TAG_DATABASE_SIZE = 64;
private static final int TAG_DATABASE_SIZE_MAX = 128;
+ private static final int TAG_DATABASE_STARTING_SIZE = 16;
private static final int LEVEL_SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK = 0;
private static final int LEVEL_PARTIAL_WAKE_LOCK = 1;
@@ -182,7 +183,7 @@ final class WakeLockLog {
* @param pw The {@code PrintWriter} to write to.
*/
public void dump(PrintWriter pw) {
- dump(pw, false);
+ dump(pw, /* includeTagDb= */ true);
}
@VisibleForTesting
@@ -1161,15 +1162,16 @@ final class WakeLockLog {
*/
static class TagDatabase {
private final int mInvalidIndex;
- private final TagData[] mArray;
+ private final int mMaxArraySize;
+ private TagData[] mArray;
private Callback mCallback;
TagDatabase(Injector injector) {
- int size = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX);
-
- // Largest possible index used as "INVALID", hence the (size - 1) sizing.
- mArray = new TagData[size - 1];
- mInvalidIndex = size - 1;
+ // Largest possible index used as "INVALID", hence the (size - 1) sizing
+ mMaxArraySize = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX - 1);
+ int startingSize = Math.min(mMaxArraySize, injector.getTagDatabaseStartingSize());
+ mArray = new TagData[startingSize];
+ mInvalidIndex = mMaxArraySize;
}
@Override
@@ -1195,8 +1197,10 @@ final class WakeLockLog {
sb.append(", entries: ").append(entries);
sb.append(", Bytes used: ").append(byteEstimate);
if (DEBUG) {
- sb.append(", Avg tag size: ").append(tagSize / tags);
- sb.append("\n ").append(Arrays.toString(mArray));
+ sb.append(", Avg tag size: ").append(tags == 0 ? 0 : (tagSize / tags));
+ for (int i = 0; i < mArray.length; i++) {
+ sb.append("\n [").append(i).append("] ").append(mArray[i]);
+ }
}
return sb.toString();
}
@@ -1284,6 +1288,18 @@ final class WakeLockLog {
return null;
}
+ // We don't have a spot available, see if we can still increase the array size
+ if (firstAvailable == -1) {
+ if (mArray.length < mMaxArraySize) {
+ int oldSize = mArray.length;
+ int newSize = Math.min(oldSize * 2, mMaxArraySize);
+ TagData[] newArray = new TagData[newSize];
+ System.arraycopy(mArray, 0, newArray, 0, oldSize);
+ mArray = newArray;
+ firstAvailable = oldSize;
+ }
+ }
+
// If we need to remove an index, report to listeners that we are removing an index.
boolean useOldest = firstAvailable == -1;
if (useOldest && mCallback != null) {
@@ -1402,6 +1418,10 @@ final class WakeLockLog {
return TAG_DATABASE_SIZE;
}
+ public int getTagDatabaseStartingSize() {
+ return TAG_DATABASE_STARTING_SIZE;
+ }
+
public int getLogSize() {
return LOG_SIZE;
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
index 5563f98e8842..7cd9bdbc662c 100644
--- a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
+++ b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
@@ -374,6 +374,10 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto
@SuppressWarnings("unchecked")
@Override
public List<BatteryHistoryFragment> getFragments() {
+ if (!mLock.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Reading battery history without a lock");
+ }
+
ensureInitialized();
return (List<BatteryHistoryFragment>)
(List<? extends BatteryHistoryFragment>) mHistoryFiles;
@@ -443,44 +447,6 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto
}
@Override
- public BatteryHistoryFragment getNextFragment(BatteryHistoryFragment current, long startTimeMs,
- long endTimeMs) {
- ensureInitialized();
-
- if (!mLock.isHeldByCurrentThread()) {
- throw new IllegalStateException("Iterating battery history without a lock");
- }
-
- int nextFileIndex = 0;
- int firstFileIndex = 0;
- // skip the last file because its data is in history buffer.
- int lastFileIndex = mHistoryFiles.size() - 2;
- for (int i = lastFileIndex; i >= 0; i--) {
- BatteryHistoryFragment fragment = mHistoryFiles.get(i);
- if (current != null && fragment.monotonicTimeMs == current.monotonicTimeMs) {
- nextFileIndex = i + 1;
- }
- if (fragment.monotonicTimeMs > endTimeMs) {
- lastFileIndex = i - 1;
- }
- if (fragment.monotonicTimeMs <= startTimeMs) {
- firstFileIndex = i;
- break;
- }
- }
-
- if (nextFileIndex < firstFileIndex) {
- nextFileIndex = firstFileIndex;
- }
-
- if (nextFileIndex <= lastFileIndex) {
- return mHistoryFiles.get(nextFileIndex);
- }
-
- return null;
- }
-
- @Override
public boolean hasCompletedFragments() {
ensureInitialized();
diff --git a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
index 799157520ca5..800fc7c25de5 100644
--- a/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
+++ b/services/core/java/com/android/server/security/CertificateRevocationStatusManager.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.os.Binder;
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Slog;
@@ -119,7 +120,7 @@ class CertificateRevocationStatusManager {
} catch (IOException | JSONException ex) {
Slog.d(TAG, "Fallback to check stored revocation status", ex);
if (ex instanceof IOException && mShouldScheduleJob) {
- scheduleJobToFetchRemoteRevocationJob();
+ Binder.withCleanCallingIdentity(this::scheduleJobToFetchRemoteRevocationJob);
}
try {
revocationList = getStoredRevocationList();
@@ -210,7 +211,7 @@ class CertificateRevocationStatusManager {
return;
}
Slog.d(TAG, "Scheduling job to fetch remote CRL.");
- jobScheduler.schedule(
+ jobScheduler.forNamespace(TAG).schedule(
new JobInfo.Builder(
JOB_ID,
new ComponentName(
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/UsbDataAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/UsbDataAdvancedProtectionHook.java
index 9a9c56f7bd17..55a8f7e6ea54 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/UsbDataAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/UsbDataAdvancedProtectionHook.java
@@ -20,6 +20,8 @@ import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_USER_PRESENT;
import static android.hardware.usb.UsbManager.ACTION_USB_PORT_CHANGED;
import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_USB;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE;
+import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -33,15 +35,18 @@ import android.hardware.usb.ParcelableUsbPort;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.IUsbManagerInternal;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
+import com.android.server.LocalServices;
import java.lang.Runnable;
import java.util.function.Consumer;
@@ -52,6 +57,7 @@ import android.security.advancedprotection.AdvancedProtectionFeature;
import com.android.internal.R;
import java.util.Map;
+import java.util.Objects;
/**
* AAPM Feature for managing and protecting USB data signal from attacks.
@@ -65,11 +71,13 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
private static final String CHANNEL_NAME = "BackgroundInstallUiNotificationChannel";
private static final int APM_USB_FEATURE_CHANNEL_ID = 1;
private static final int DELAY_DISABLE_MS = 1000;
+ private static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
private final Context mContext;
private final Handler mDelayedDisableHandler = new Handler(Looper.getMainLooper());
private UsbManager mUsbManager;
+ private IUsbManagerInternal mUsbManagerInternal;
private BroadcastReceiver mUsbProtectionBroadcastReceiver;
private KeyguardManager mKeyguardManager;
private NotificationManager mNotificationManager;
@@ -85,6 +93,8 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
super(context, enabled);
mContext = context;
mUsbManager = mContext.getSystemService(UsbManager.class);
+ mUsbManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(IUsbManagerInternal.class));
onAdvancedProtectionChanged(enabled);
}
@@ -117,9 +127,7 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
if (mBroadcastReceiverIsRegistered) {
unregisterReceiver();
}
- if (!mUsbManager.enableUsbDataSignal(true)) {
- Slog.e(TAG, "USB Data protection toggle failed");
- }
+ setUsbDataSignalIfPossible(true);
}
}
@@ -134,23 +142,41 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
if (ACTION_USER_PRESENT.equals(intent.getAction())
&& !mKeyguardManager.isKeyguardLocked()) {
mDelayedDisableHandler.removeCallbacksAndMessages(null);
- setUsbDataSignalIfNoConnectedDevices(true);
+ setUsbDataSignalIfPossible(true);
+
} else if (ACTION_SCREEN_OFF.equals(intent.getAction())
&& mKeyguardManager.isKeyguardLocked()) {
- setUsbDataSignalIfNoConnectedDevices(false);
+ setUsbDataSignalIfPossible(false);
+
} else if (ACTION_USB_PORT_CHANGED.equals(intent.getAction())) {
if (Build.IS_DEBUGGABLE) {
dumpUsbDevices();
}
- setDelayedDisableTaskIfDisconnectedAndLocked(intent);
+ if(mKeyguardManager.isKeyguardLocked()) {
+ updateDelayedDisableTask(intent);
+ }
sendNotificationIfDeviceLocked(intent);
+
}
} catch (Exception e) {
Slog.e(TAG, "USB Data protection failed with: " + e.getMessage());
}
}
- private boolean getUsbPortStatusIsConnectedAndDataEnabled(Intent intent) {
+ private void updateDelayedDisableTask(Intent intent) {
+ // For recovered intermittent/unreliable USB connections
+ if(usbPortIsConnectedAndDataEnabled(intent)) {
+ mDelayedDisableHandler.removeCallbacksAndMessages(null);
+ } else if(!mDelayedDisableHandler.hasMessagesOrCallbacks()) {
+ mDelayedDisableHandler.postDelayed(() -> {
+ if (mKeyguardManager.isKeyguardLocked()) {
+ setUsbDataSignalIfPossible(false);
+ }
+ }, DELAY_DISABLE_MS);
+ }
+ }
+
+ private boolean usbPortIsConnectedAndDataEnabled(Intent intent) {
UsbPortStatus portStatus =
intent.getParcelableExtra(
UsbManager.EXTRA_PORT_STATUS, UsbPortStatus.class);
@@ -160,40 +186,7 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
!= UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
}
- private void setDelayedDisableTaskIfDisconnectedAndLocked(Intent intent) {
- if(mKeyguardManager.isKeyguardLocked()) {
- if(getUsbPortStatusIsConnectedAndDataEnabled(intent)) {
- mDelayedDisableHandler.removeCallbacksAndMessages(null);
- } else if(!mDelayedDisableHandler.hasMessagesOrCallbacks()) {
- mDelayedDisableHandler.postDelayed(() -> {
- disableChangedUsbPortIfDisconnected(intent);
- }, DELAY_DISABLE_MS);
- }
- }
- }
-
- private void disableChangedUsbPortIfDisconnected(Intent intent) {
- UsbPortStatus portStatus =
- intent.getParcelableExtra(
- UsbManager.EXTRA_PORT_STATUS, UsbPortStatus.class);
- if (Build.IS_DEBUGGABLE) {
- Slog.i(
- TAG,
- "disableChangedUsbPortIfDisconnected: " + portStatus == null
- ? "null"
- : portStatus.toString());
- }
-
- if (mKeyguardManager.isKeyguardLocked()
- && portStatus != null && !portStatus.isConnected()
- ) {
- intent.getParcelableExtra(
- UsbManager.EXTRA_PORT, ParcelableUsbPort.class)
- .getUsbPort(mUsbManager)
- .enableUsbData(false);
- }
- }
-
+ // TODO: b/401540215 Remove this as part of pre-release cleanup
private void dumpUsbDevices() {
Slog.d(TAG, "dumpUsbDevices: ");
Map<String, UsbDevice> portStatusMap = mUsbManager.getDeviceList();
@@ -238,9 +231,7 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
UsbPortStatus portStatus =
intent.getParcelableExtra(UsbManager.EXTRA_PORT_STATUS, UsbPortStatus.class);
if (mKeyguardManager.isKeyguardLocked()
- && portStatus != null
- && portStatus.isConnected()
- && portStatus.getUsbDataStatus() == UsbPortStatus.DATA_STATUS_DISABLED_FORCE) {
+ && usbPortIsConnectedWithDataDisabled(portStatus)) {
sendNotification(
mContext.getString(
R.string.usb_apm_usb_plugged_in_when_locked_notification_title),
@@ -251,39 +242,46 @@ public class UsbDataAdvancedProtectionHook extends AdvancedProtectionHook {
}
}
- private void setUsbDataSignalIfNoConnectedDevices(boolean status) {
- // disable all ports that don't have an active data connection
- if (!status) {
- for (UsbPort usbPort : mUsbManager.getPorts()) {
- if (Build.IS_DEBUGGABLE) {
- Slog.i(
- TAG,
- "setUsbDataSignal: false " + usbPort.getStatus() == null
- ? "null"
- : usbPort.getStatus().toString());
- }
- if (usbPort.getStatus() == null
- || !usbPort.getStatus().isConnected()
- || usbPort.getStatus().getCurrentDataRole()
- == UsbPortStatus.DATA_ROLE_NONE) {
- usbPort.enableUsbData(false);
- }
- }
+ private boolean usbPortIsConnectedWithDataDisabled(UsbPortStatus portStatus) {
+ return portStatus != null
+ && portStatus.isConnected()
+ && portStatus.getUsbDataStatus() == DATA_STATUS_DISABLED_FORCE;
+ }
+
+ private void setUsbDataSignalIfPossible(boolean status) {
+ if (!status && deviceHaveUsbDataConnection()) {
+ return;
}
- // Always re-enable all if true
- else {
- if (!mUsbManager.enableUsbDataSignal(status)) {
+ try {
+ if (!mUsbManagerInternal.enableUsbDataSignal(status,
+ OS_USB_DISABLE_REASON_LOCKDOWN_MODE)) {
Slog.e(TAG, "USB Data protection toggle failed");
}
- for (UsbPort usbPort : mUsbManager.getPorts()) {
- usbPort.resetUsbPort(mContext.getMainExecutor(),
- new Consumer<Integer>() {
- public void accept(Integer status) {
- Slog.i(TAG, "Consumer status: " + status);
- }
- });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException thrown when calling enableUsbDataSignal", e);
+ }
+ }
+
+ private boolean deviceHaveUsbDataConnection() {
+ for (UsbPort usbPort : mUsbManager.getPorts()) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.i(
+ TAG,
+ "setUsbDataSignal: false, Port status: " + usbPort.getStatus() == null
+ ? "null"
+ : usbPort.getStatus().toString());
+ }
+ if (usbPortIsConnectedWithDataEnabled(usbPort)) {
+ return true;
}
}
+ return false;
+ }
+
+ private boolean usbPortIsConnectedWithDataEnabled(UsbPort usbPort) {
+ return usbPort.getStatus() != null
+ && usbPort.getStatus().isConnected()
+ && usbPort.getStatus().getCurrentDataRole() != DATA_ROLE_NONE;
}
private void registerReceiver() {
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
index 54365ff03db0..c5a43a57da82 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -72,14 +72,20 @@ class SelinuxAuditLogsCollector {
}
SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
- this(
- () ->
- DeviceConfig.getString(
- DeviceConfig.NAMESPACE_ADSERVICES,
- CONFIG_SELINUX_AUDIT_DOMAIN,
- DEFAULT_SELINUX_AUDIT_DOMAIN),
- rateLimiter,
- quotaLimiter);
+ this(new DefaultDomainSupplier(), rateLimiter, quotaLimiter);
+ }
+
+ private static class DefaultDomainSupplier implements Supplier<String> {
+ @Override
+ public String get() {
+ if (SelinuxAuditLogsService.enabledForAllDomains()) {
+ return "\\w+";
+ }
+ return DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_DOMAIN,
+ DEFAULT_SELINUX_AUDIT_DOMAIN);
+ }
}
public void setStopRequested(boolean stopRequested) {
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
index d46e8916d9e9..9dc457c5d63b 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -16,6 +16,7 @@
package com.android.server.selinux;
import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+import static com.android.server.selinux.flags.Flags.selinuxLogsCollect;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -49,6 +50,9 @@ public class SelinuxAuditLogsService extends JobService {
"selinux_audit_job_frequency_hours";
private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job";
private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap";
+ private static final String DEVICE_CONFIG_SECURITY_NAMESPACE = "security";
+ private static final String CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED =
+ "selinux_audit_job_enabled";
private static final int MAX_PERMITS_CAP_DEFAULT = 50000;
private static final int SELINUX_AUDIT_JOB_ID = 25327386;
@@ -76,7 +80,7 @@ public class SelinuxAuditLogsService extends JobService {
/** Schedule jobs with the {@link JobScheduler}. */
public static void schedule(Context context) {
- if (!selinuxSdkSandboxAudit()) {
+ if (!selinuxSdkSandboxAudit() && !enabledForAllDomains()) {
Slog.d(TAG, "SelinuxAuditLogsService not enabled");
return;
}
@@ -86,13 +90,20 @@ public class SelinuxAuditLogsService extends JobService {
return;
}
- LogsCollectorJobScheduler propertiesListener =
+ LogsCollectorJobScheduler scheduler =
new LogsCollectorJobScheduler(
context.getSystemService(JobScheduler.class)
.forNamespace(SELINUX_AUDIT_NAMESPACE));
- propertiesListener.schedule();
+ scheduler.schedule();
+
+ AdServicesPropertyMonitor adServicesProperties = new AdServicesPropertyMonitor(scheduler);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), adServicesProperties);
+
+ SecurityPropertyMonitor securityProperties = new SecurityPropertyMonitor(scheduler);
DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener);
+ DEVICE_CONFIG_SECURITY_NAMESPACE, context.getMainExecutor(), securityProperties);
+
}
@Override
@@ -101,7 +112,7 @@ public class SelinuxAuditLogsService extends JobService {
Slog.e(TAG, "The job id does not match the expected selinux job id.");
return false;
}
- if (!selinuxSdkSandboxAudit()) {
+ if (!selinuxSdkSandboxAudit() && !enabledForAllDomains()) {
Slog.i(TAG, "Selinux audit job disabled.");
return false;
}
@@ -123,17 +134,33 @@ public class SelinuxAuditLogsService extends JobService {
return false;
}
- /**
- * This class is in charge of scheduling the job service, and keeping the scheduling up to date
- * when the parameters change.
- */
- private static final class LogsCollectorJobScheduler
+ /** Checks if the service is enabled for all domains */
+ public static final boolean enabledForAllDomains() {
+ if (selinuxLogsCollect()) {
+ return DeviceConfig.getBoolean(
+ DEVICE_CONFIG_SECURITY_NAMESPACE,
+ CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED,
+ false);
+ }
+ return false;
+ }
+
+ /** Checks if the service is enabled for SDK Sandbox */
+ public static final boolean enabledForSdkSandbox() {
+ if (selinuxSdkSandboxAudit()) {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES, CONFIG_SELINUX_ENABLE_AUDIT_JOB, false);
+ }
+ return false;
+ }
+
+ private static final class AdServicesPropertyMonitor
implements DeviceConfig.OnPropertiesChangedListener {
- private final JobScheduler mJobScheduler;
+ private final LogsCollectorJobScheduler mScheduler;
- private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
- mJobScheduler = jobScheduler;
+ private AdServicesPropertyMonitor(LogsCollectorJobScheduler scheduler) {
+ mScheduler = scheduler;
}
@Override
@@ -149,19 +176,65 @@ public class SelinuxAuditLogsService extends JobService {
if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) {
boolean enabled =
changedProperties.getBoolean(
- CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false);
+ CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false)
+ || enabledForAllDomains();
if (enabled) {
- schedule();
+ mScheduler.schedule();
} else {
- mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
+ mScheduler.cancel();
}
} else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) {
// The job frequency changed, reschedule.
- schedule();
+ mScheduler.schedule();
}
}
+ }
+
+ private static final class SecurityPropertyMonitor
+ implements DeviceConfig.OnPropertiesChangedListener {
+
+ private final LogsCollectorJobScheduler mScheduler;
+
+ private SecurityPropertyMonitor(LogsCollectorJobScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ @Override
+ public void onPropertiesChanged(Properties changedProperties) {
+ Set<String> keyset = changedProperties.getKeyset();
+
+ if (keyset.contains(CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED)) {
+ boolean enabled =
+ changedProperties.getBoolean(
+ CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED,
+ /* defaultValue= */ false)
+ || enabledForSdkSandbox();
+ if (enabled) {
+ mScheduler.schedule();
+ } else {
+ mScheduler.cancel();
+ }
+ }
+ }
+ }
+
+ /**
+ * This class is in charge of scheduling the job service, and keeping the scheduling up to date
+ * when the parameters change.
+ */
+ private static final class LogsCollectorJobScheduler {
+
+ private final JobScheduler mJobScheduler;
+
+ private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
+ mJobScheduler = jobScheduler;
+ }
+
+ public void cancel() {
+ mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
+ }
- private void schedule() {
+ public void schedule() {
long frequencyMillis =
TimeUnit.HOURS.toMillis(
DeviceConfig.getInt(
diff --git a/services/core/java/com/android/server/selinux/flags.aconfig b/services/core/java/com/android/server/selinux/flags.aconfig
new file mode 100644
index 000000000000..3bb5a6bda1de
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.selinux.flags"
+container: "system"
+
+flag {
+ name: "selinux_logs_collect"
+ namespace: "network_security"
+ description: "Enable collection of SELinux denials based on selinux_audit_job_enabled"
+ bug: "372950125"
+}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 27ca83addec8..126b3a7c721f 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -351,18 +351,38 @@ public final class StorageUserConnection {
}
}
+
private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
+ waitForAsyncVoid(asyncCall, /*bindIfNotConnected*/ true,
+ DEFAULT_REMOTE_TIMEOUT_SECONDS);
+ }
+
+ private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall,
+ boolean bindIfNotConnected, int timeoutSeconds) throws Exception {
CompletableFuture<Void> opFuture = new CompletableFuture<>();
RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
- waitForAsync(asyncCall, callback, opFuture, mOutstandingOps,
- DEFAULT_REMOTE_TIMEOUT_SECONDS);
+ waitForAsync(asyncCall, callback, opFuture, mOutstandingOps, bindIfNotConnected,
+ timeoutSeconds);
}
private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback,
CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps,
- long timeoutSeconds) throws Exception {
- CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+ boolean bindIfNotConnected, long timeoutSeconds) throws Exception {
+
+ CompletableFuture<IExternalStorageService> serviceFuture;
+ if (bindIfNotConnected) {
+ serviceFuture = connectIfNeeded();
+ } else {
+ synchronized (mLock) {
+ if (mRemoteFuture == null || mRemoteFuture.getNow(null) == null) {
+ Slog.w(TAG, "Dropping async request as service is not connected"
+ + "and request doesn't require connecting");
+ return null;
+ }
+ serviceFuture = mRemoteFuture;
+ }
+ }
try {
synchronized (mLock) {
@@ -404,7 +424,11 @@ public final class StorageUserConnection {
public void endSession(Session session) throws ExternalStorageServiceException {
try {
waitForAsyncVoid((service, callback) ->
- service.endSession(session.sessionId, callback));
+ service.endSession(session.sessionId, callback),
+ // endSession shouldn't be trying to bind to remote service if the service
+ // isn't connected already as this means that no previous mounting has been
+ // completed.
+ /*bindIfNotConnected*/ false, /*timeoutSeconds*/ 10);
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to end session: " + session, e);
}
@@ -415,7 +439,11 @@ public final class StorageUserConnection {
ExternalStorageServiceException {
try {
waitForAsyncVoid((service, callback) ->
- service.notifyVolumeStateChanged(sessionId, vol, callback));
+ service.notifyVolumeStateChanged(sessionId, vol, callback),
+ // notifyVolumeStateChanged shouldn't be trying to bind to remote service
+ // if the service isn't connected already as this means that
+ // no previous mounting has been completed
+ /*bindIfNotConnected*/ false, /*timeoutSeconds*/ 10);
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to notify volume state changed "
+ "for vol : " + vol, e);
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 31348cd9156f..17980c02502f 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -177,6 +177,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
private final String mDefaultTextClassifierPackage;
@Nullable
private final String mSystemTextClassifierPackage;
+ private final MyPackageMonitor mPackageMonitor;
private TextClassificationManagerService(Context context) {
mContext = Objects.requireNonNull(context);
@@ -187,50 +188,50 @@ public final class TextClassificationManagerService extends ITextClassifierServi
mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName();
mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName();
mSessionCache = new SessionCache(mLock);
+ mPackageMonitor = new MyPackageMonitor();
}
private void startListenSettings() {
mSettingsListener.registerObserver();
}
- void startTrackingPackageChanges() {
- final PackageMonitor monitor = new PackageMonitor() {
-
- @Override
- public void onPackageAdded(String packageName, int uid) {
- notifyPackageInstallStatusChange(packageName, /* installed*/ true);
- }
+ private class MyPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ notifyPackageInstallStatusChange(packageName, /* installed*/ true);
+ }
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- notifyPackageInstallStatusChange(packageName, /* installed= */ false);
- }
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ notifyPackageInstallStatusChange(packageName, /* installed= */ false);
+ }
- @Override
- public void onPackageModified(String packageName) {
- final int userId = getChangingUserId();
- synchronized (mLock) {
- final UserState userState = getUserStateLocked(userId);
- final ServiceState serviceState = userState.getServiceStateLocked(packageName);
- if (serviceState != null) {
- serviceState.onPackageModifiedLocked();
- }
+ @Override
+ public void onPackageModified(String packageName) {
+ final int userId = getChangingUserId();
+ synchronized (mLock) {
+ final UserState userState = getUserStateLocked(userId);
+ final ServiceState serviceState = userState.getServiceStateLocked(packageName);
+ if (serviceState != null) {
+ serviceState.onPackageModifiedLocked();
}
}
+ }
- private void notifyPackageInstallStatusChange(String packageName, boolean installed) {
- final int userId = getChangingUserId();
- synchronized (mLock) {
- final UserState userState = getUserStateLocked(userId);
- final ServiceState serviceState = userState.getServiceStateLocked(packageName);
- if (serviceState != null) {
- serviceState.onPackageInstallStatusChangeLocked(installed);
- }
+ private void notifyPackageInstallStatusChange(String packageName, boolean installed) {
+ final int userId = getChangingUserId();
+ synchronized (mLock) {
+ final UserState userState = getUserStateLocked(userId);
+ final ServiceState serviceState = userState.getServiceStateLocked(packageName);
+ if (serviceState != null) {
+ serviceState.onPackageInstallStatusChangeLocked(installed);
}
}
- };
+ }
+ }
- monitor.register(mContext, null, UserHandle.ALL, true);
+ void startTrackingPackageChanges() {
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
}
@Override
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 8bcf1a9be031..47d6879129ee 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -183,7 +183,7 @@ public final class TvInputManagerService extends SystemService {
private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
private final MessageHandler mMessageHandler;
-
+ private final MyPackageMonitor mPackageMonitor;
private final ActivityManager mActivityManager;
private boolean mExternalInputLoggingDisplayNameFilterEnabled = false;
@@ -200,6 +200,7 @@ public final class TvInputManagerService extends SystemService {
mMessageHandler =
new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
+ mPackageMonitor = new MyPackageMonitor(/* supportsPackageRestartQuery */ true);
mActivityManager =
(ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
@@ -298,74 +299,79 @@ public final class TvInputManagerService extends SystemService {
mExternalInputLoggingDeviceBrandNames.addAll(Arrays.asList(deviceBrandNames));
}
- private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) {
- private void buildTvInputList(String[] packages) {
- int userId = getChangingUserId();
- synchronized (mLock) {
- if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
- buildTvInputListLocked(userId, packages);
- buildTvContentRatingSystemListLocked(userId);
- }
+ private class MyPackageMonitor extends PackageMonitor {
+ MyPackageMonitor(boolean supportsPackageRestartQuery) {
+ super(supportsPackageRestartQuery);
+ }
+
+ private void buildTvInputList(String[] packages) {
+ int userId = getChangingUserId();
+ synchronized (mLock) {
+ if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+ buildTvInputListLocked(userId, packages);
+ buildTvContentRatingSystemListLocked(userId);
}
}
+ }
- @Override
- public void onPackageUpdateFinished(String packageName, int uid) {
- if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
- // This callback is invoked when the TV input is reinstalled.
- // In this case, isReplacing() always returns true.
- buildTvInputList(new String[] { packageName });
- }
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
+ // This callback is invoked when the TV input is reinstalled.
+ // In this case, isReplacing() always returns true.
+ buildTvInputList(new String[] { packageName });
+ }
- @Override
- public void onPackagesAvailable(String[] packages) {
- if (DEBUG) {
- Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
- }
- // This callback is invoked when the media on which some packages exist become
- // available.
- if (isReplacing()) {
- buildTvInputList(packages);
- }
+ @Override
+ public void onPackagesAvailable(String[] packages) {
+ if (DEBUG) {
+ Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
}
+ // This callback is invoked when the media on which some packages exist become
+ // available.
+ if (isReplacing()) {
+ buildTvInputList(packages);
+ }
+ }
- @Override
- public void onPackagesUnavailable(String[] packages) {
- // This callback is invoked when the media on which some packages exist become
- // unavailable.
- if (DEBUG) {
- Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
- + ")");
- }
- if (isReplacing()) {
- buildTvInputList(packages);
- }
+ @Override
+ public void onPackagesUnavailable(String[] packages) {
+ // This callback is invoked when the media on which some packages exist become
+ // unavailable.
+ if (DEBUG) {
+ Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
+ + ")");
}
+ if (isReplacing()) {
+ buildTvInputList(packages);
+ }
+ }
- @Override
- public void onSomePackagesChanged() {
- // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
- // the TV inputs.
- if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
- if (isReplacing()) {
- if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
- // When the package is updated, buildTvInputListLocked is called in other
- // methods instead.
- return;
- }
- buildTvInputList(null);
+ @Override
+ public void onSomePackagesChanged() {
+ // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
+ // the TV inputs.
+ if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
+ if (isReplacing()) {
+ if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
+ // When the package is updated, buildTvInputListLocked is called in other
+ // methods instead.
+ return;
}
+ buildTvInputList(null);
+ }
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- // The input list needs to be updated in any cases, regardless of whether
- // it happened to the whole package or a specific component. Returning true so that
- // the update can be handled in {@link #onSomePackagesChanged}.
- return true;
- }
- };
- monitor.register(mContext, null, UserHandle.ALL, true);
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // The input list needs to be updated in any cases, regardless of whether
+ // it happened to the whole package or a specific component. Returning true so that
+ // the update can be handled in {@link #onSomePackagesChanged}.
+ return true;
+ }
+ }
+
+ private void registerBroadcastReceivers() {
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 6a7fc6dcf7cd..42013fab7a14 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -105,6 +105,7 @@ public class TvInteractiveAppManagerService extends SystemService {
// A global lock.
private final Object mLock = new Object();
private final Context mContext;
+ private final MyPackageMonitor mPackageMonitor;
// ID of the current user.
@GuardedBy("mLock")
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -138,6 +139,7 @@ public class TvInteractiveAppManagerService extends SystemService {
super(context);
mContext = context;
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+ mPackageMonitor = new MyPackageMonitor(/* supportsPackageRestartQuery */ true);
}
@GuardedBy("mLock")
@@ -518,86 +520,91 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
- private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) {
- private void buildTvInteractiveAppServiceList(String[] packages) {
- int userId = getChangingUserId();
- synchronized (mLock) {
- if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
- buildTvInteractiveAppServiceListLocked(userId, packages);
- buildAppLinkInfoLocked(userId);
- }
+ private class MyPackageMonitor extends PackageMonitor {
+ MyPackageMonitor(boolean supportsPackageRestartQuery) {
+ super(supportsPackageRestartQuery);
+ }
+
+ private void buildTvInteractiveAppServiceList(String[] packages) {
+ int userId = getChangingUserId();
+ synchronized (mLock) {
+ if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+ buildTvInteractiveAppServiceListLocked(userId, packages);
+ buildAppLinkInfoLocked(userId);
}
}
- private void buildTvAdServiceList(String[] packages) {
- int userId = getChangingUserId();
- synchronized (mLock) {
- if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
- buildTvAdServiceListLocked(userId, packages);
- }
+ }
+ private void buildTvAdServiceList(String[] packages) {
+ int userId = getChangingUserId();
+ synchronized (mLock) {
+ if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+ buildTvAdServiceListLocked(userId, packages);
}
}
+ }
- @Override
- public void onPackageUpdateFinished(String packageName, int uid) {
- if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
- // This callback is invoked when the TV interactive App service is reinstalled.
- // In this case, isReplacing() always returns true.
- buildTvInteractiveAppServiceList(new String[] { packageName });
- buildTvAdServiceList(new String[] { packageName });
- }
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
+ // This callback is invoked when the TV interactive App service is reinstalled.
+ // In this case, isReplacing() always returns true.
+ buildTvInteractiveAppServiceList(new String[] { packageName });
+ buildTvAdServiceList(new String[] { packageName });
+ }
- @Override
- public void onPackagesAvailable(String[] packages) {
- if (DEBUG) {
- Slogf.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
- }
- // This callback is invoked when the media on which some packages exist become
- // available.
- if (isReplacing()) {
- buildTvInteractiveAppServiceList(packages);
- buildTvAdServiceList(packages);
- }
+ @Override
+ public void onPackagesAvailable(String[] packages) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
}
+ // This callback is invoked when the media on which some packages exist become
+ // available.
+ if (isReplacing()) {
+ buildTvInteractiveAppServiceList(packages);
+ buildTvAdServiceList(packages);
+ }
+ }
- @Override
- public void onPackagesUnavailable(String[] packages) {
- // This callback is invoked when the media on which some packages exist become
- // unavailable.
- if (DEBUG) {
- Slogf.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
- + ")");
- }
- if (isReplacing()) {
- buildTvInteractiveAppServiceList(packages);
- buildTvAdServiceList(packages);
- }
+ @Override
+ public void onPackagesUnavailable(String[] packages) {
+ // This callback is invoked when the media on which some packages exist become
+ // unavailable.
+ if (DEBUG) {
+ Slogf.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
+ + ")");
}
+ if (isReplacing()) {
+ buildTvInteractiveAppServiceList(packages);
+ buildTvAdServiceList(packages);
+ }
+ }
- @Override
- public void onSomePackagesChanged() {
- if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()");
- if (isReplacing()) {
- if (DEBUG) {
- Slogf.d(TAG, "Skipped building TV interactive App list due to replacing");
- }
- // When the package is updated, buildTvInteractiveAppServiceListLocked is called
- // in other methods instead.
- return;
+ @Override
+ public void onSomePackagesChanged() {
+ if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()");
+ if (isReplacing()) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Skipped building TV interactive App list due to replacing");
}
- buildTvInteractiveAppServiceList(null);
- buildTvAdServiceList(null);
+ // When the package is updated, buildTvInteractiveAppServiceListLocked is called
+ // in other methods instead.
+ return;
}
+ buildTvInteractiveAppServiceList(null);
+ buildTvAdServiceList(null);
+ }
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- // The interactive App list needs to be updated in any cases, regardless of whether
- // it happened to the whole package or a specific component. Returning true so that
- // the update can be handled in {@link #onSomePackagesChanged}.
- return true;
- }
- };
- monitor.register(mContext, null, UserHandle.ALL, true);
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // The interactive App list needs to be updated in any cases, regardless of whether
+ // it happened to the whole package or a specific component. Returning true so that
+ // the update can be handled in {@link #onSomePackagesChanged}.
+ return true;
+ }
+ }
+
+ private void registerBroadcastReceivers() {
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 621a128a736e..94e8ca5464b0 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -51,7 +51,9 @@ import java.util.NoSuchElementException;
final class VendorVibrationSession extends IVibrationSession.Stub
implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
private static final String TAG = "VendorVibrationSession";
- private static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VendorVibrationSession DEBUG && adb reboot'
+ private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
/** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
interface VibratorManagerHooks {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cb9988fd698e..ab30cdc730eb 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -36,7 +36,9 @@ import java.util.Objects;
/** Plays a {@link HalVibration} in dedicated thread. */
final class VibrationThread extends Thread {
static final String TAG = "VibrationThread";
- static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VibrationThread DEBUG && adb reboot'
+ static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
/** Calls into VibratorManager functionality needed for playing a {@link HalVibration}. */
interface VibratorManagerHooks {
diff --git a/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java b/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java
new file mode 100644
index 000000000000..9f37e76f0d6d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import android.util.Log;
+
+class VibratorDebugUtils {
+
+ /**
+ * Checks if debugging is enabled for the specified tag or globally.
+ *
+ * <p>To enable debugging:<br>
+ * {@code adb shell setprop persist.log.tag.Vibrator_All DEBUG}<br>
+ * To disable debugging:<br>
+ * {@code adb shell setprop persist.log.tag.Vibrator_All \"\" }
+ *
+ * @param tag The tag to check for debugging. Use the tag name from the calling class.
+ * @return True if debugging is enabled for the tag or globally (Vibrator_All), false otherwise.
+ */
+ public static boolean isDebuggable(String tag) {
+ return Log.isLoggable(tag, Log.DEBUG) || Log.isLoggable("Vibrator_All", Log.DEBUG);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ce91e63b4849..b9530978e850 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -108,7 +108,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String VIBRATOR_CONTROL_SERVICE =
"android.frameworks.vibrator.IVibratorControlService/default";
- private static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VibratorManagerService DEBUG && adb reboot'
+ private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
diff --git a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
index 7c2ce6467122..458cb023cc4e 100644
--- a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
+++ b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
@@ -58,6 +58,7 @@ public class EnabledComponentsObserver implements SettingChangeListener {
private final Object mLock;
private final Context mContext;
+ private final PackageMonitor mPackageMonitor;
private final String mSettingName;
private final String mServiceName;
private final String mServicePermission;
@@ -78,13 +79,39 @@ public class EnabledComponentsObserver implements SettingChangeListener {
private EnabledComponentsObserver(@NonNull Context context, @NonNull String settingName,
@NonNull String servicePermission, @NonNull String serviceName, @NonNull Object lock,
- @NonNull Collection<EnabledComponentChangeListener> listeners) {
+ @NonNull Collection<EnabledComponentChangeListener> listeners,
+ @NonNull Looper looper) {
mLock = lock;
mContext = context;
mSettingName = settingName;
mServiceName = serviceName;
mServicePermission = servicePermission;
mEnabledComponentListeners.addAll(listeners);
+ mPackageMonitor = new PackageMonitor(true) {
+ @Override
+ public void onSomePackagesChanged() {
+ onPackagesChanged();
+ }
+
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ onPackagesChanged();
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ onPackagesChanged();
+ }
+
+ @Override
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid,
+ boolean doit) {
+ onPackagesChanged();
+ return super.onHandleForceStop(intent, packages, uid, doit);
+ }
+ };
+
+ mPackageMonitor.register(context, looper, UserHandle.ALL, true);;
}
/**
@@ -108,38 +135,7 @@ public class EnabledComponentsObserver implements SettingChangeListener {
SettingsObserver s = SettingsObserver.build(context, handler, settingName);
final EnabledComponentsObserver o = new EnabledComponentsObserver(context, settingName,
- servicePermission, serviceName, lock, listeners);
-
- PackageMonitor packageMonitor = new PackageMonitor(true) {
- @Override
- public void onSomePackagesChanged() {
- o.onPackagesChanged();
-
- }
-
- @Override
- public void onPackageDisappeared(String packageName, int reason) {
- o.onPackagesChanged();
-
- }
-
- @Override
- public void onPackageModified(String packageName) {
- o.onPackagesChanged();
-
- }
-
- @Override
- public boolean onHandleForceStop(Intent intent, String[] packages, int uid,
- boolean doit) {
- o.onPackagesChanged();
-
- return super.onHandleForceStop(intent, packages, uid, doit);
- }
- };
-
- packageMonitor.register(context, looper, UserHandle.ALL, true);
-
+ servicePermission, serviceName, lock, listeners, looper);
s.addListener(o);
return o;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8e8455ad5288..424439df3c4b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -17,7 +17,6 @@
package com.android.server.wallpaper;
import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
-import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
@@ -85,20 +84,11 @@ public class WallpaperCropper {
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
- /**
- * Helpers exposed to the window manager part (WallpaperController)
- */
- public interface WallpaperCropUtils {
-
- /**
- * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}
- */
- Rect getCrop(Point displaySize, Point bitmapSize,
- SparseArray<Rect> suggestedCrops, boolean rtl);
- }
+ private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo;
WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) {
mWallpaperDisplayHelper = wallpaperDisplayHelper;
+ mDefaultDisplayInfo = mWallpaperDisplayHelper.getDefaultDisplayInfo();
}
/**
@@ -116,16 +106,16 @@ public class WallpaperCropper {
* {@link #getAdjustedCrop}.
* </ul>
*
- * @param displaySize The dimensions of the surface where we want to render the wallpaper
- * @param bitmapSize The dimensions of the wallpaper bitmap
- * @param rtl Whether the device is right-to-left
- * @param suggestedCrops An optional list of user-defined crops for some orientations.
- * If there is a suggested crop for
+ * @param displaySize The dimensions of the surface where we want to render the wallpaper
+ * @param defaultDisplayInfo The default display info
+ * @param bitmapSize The dimensions of the wallpaper bitmap
+ * @param rtl Whether the device is right-to-left
+ * @param suggestedCrops An optional list of user-defined crops for some orientations.
*
* @return A Rect indicating how to crop the bitmap for the current display.
*/
- public Rect getCrop(Point displaySize, Point bitmapSize,
- SparseArray<Rect> suggestedCrops, boolean rtl) {
+ public static Rect getCrop(Point displaySize, WallpaperDefaultDisplayInfo defaultDisplayInfo,
+ Point bitmapSize, SparseArray<Rect> suggestedCrops, boolean rtl) {
int orientation = getOrientation(displaySize);
@@ -135,23 +125,24 @@ public class WallpaperCropper {
// The first exception is if the device is a foldable and we're on the folded screen.
// In that case, show the center of what's on the unfolded screen.
- int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+ int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation);
if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
// Let the system know that we're showing the full image on the unfolded screen
SparseArray<Rect> newSuggestedCrops = new SparseArray<>();
newSuggestedCrops.put(unfoldedOrientation, crop);
// This will fall into "Case 4" of this function and center the folded screen
- return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
+ return getCrop(displaySize, defaultDisplayInfo, bitmapSize, newSuggestedCrops,
+ rtl);
}
// The second exception is if we're on tablet and we're on portrait mode.
// In that case, center the wallpaper relatively to landscape and put some parallax.
- boolean isTablet = mWallpaperDisplayHelper.isLargeScreen()
- && !mWallpaperDisplayHelper.isFoldable();
+ boolean isTablet = defaultDisplayInfo.isLargeScreen && !defaultDisplayInfo.isFoldable;
if (isTablet && displaySize.x < displaySize.y) {
Point rotatedDisplaySize = new Point(displaySize.y, displaySize.x);
// compute the crop on landscape (without parallax)
- Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+ Rect landscapeCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl);
landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl);
// compute the crop on portrait at the center of the landscape crop
crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD);
@@ -173,7 +164,8 @@ public class WallpaperCropper {
if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
|| testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
- return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(),
+ rtl);
}
}
@@ -185,10 +177,9 @@ public class WallpaperCropper {
// Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
// trying to preserve the zoom level and the center of the image
- SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
int rotatedOrientation = getRotatedOrientation(orientation);
suggestedCrop = suggestedCrops.get(rotatedOrientation);
- Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+ Point suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation);
if (suggestedCrop != null) {
// only keep the visible part (without parallax)
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
@@ -197,9 +188,9 @@ public class WallpaperCropper {
// Case 4: if the device is a foldable, if we're looking for a folded orientation and have
// the suggested crop of the relative unfolded orientation, reuse it by removing content.
- int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+ int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation);
suggestedCrop = suggestedCrops.get(unfoldedOrientation);
- suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
+ suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(unfoldedOrientation);
if (suggestedCrop != null) {
// compute the visible part (without parallax) of the unfolded screen
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
@@ -207,8 +198,11 @@ public class WallpaperCropper {
Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
// if we removed some width, add it back to add a parallax effect
if (res.width() < adjustedCrop.width()) {
- if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
- else res.right = Math.max(res.right, adjustedCrop.right);
+ if (rtl) {
+ res.left = Math.min(res.left, adjustedCrop.left);
+ } else {
+ res.right = Math.max(res.right, adjustedCrop.right);
+ }
// use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
}
@@ -218,9 +212,9 @@ public class WallpaperCropper {
// Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
// have the suggested crop of the relative folded orientation, reuse it by adding content.
- int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
+ int foldedOrientation = defaultDisplayInfo.getFoldedOrientation(orientation);
suggestedCrop = suggestedCrops.get(foldedOrientation);
- suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation);
+ suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(foldedOrientation);
if (suggestedCrop != null) {
// only keep the visible part (without parallax)
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
@@ -229,17 +223,19 @@ public class WallpaperCropper {
// Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
// rotate, then fold or unfold
- Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+ Point rotatedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation);
if (rotatedDisplaySize != null) {
- int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation);
- int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation);
+ int rotatedFolded = defaultDisplayInfo.getFoldedOrientation(rotatedOrientation);
+ int rotateUnfolded = defaultDisplayInfo.getUnfoldedOrientation(rotatedOrientation);
for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) {
suggestedCrop = suggestedCrops.get(suggestedOrientation);
if (suggestedCrop != null) {
- Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+ Rect rotatedCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl);
SparseArray<Rect> rotatedCropMap = new SparseArray<>();
rotatedCropMap.put(rotatedOrientation, rotatedCrop);
- return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl);
+ return getCrop(displaySize, defaultDisplayInfo, bitmapSize, rotatedCropMap,
+ rtl);
}
}
}
@@ -248,8 +244,8 @@ public class WallpaperCropper {
Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize
+ ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops
+ ", orientation: " + orientation + ", rtl: " + rtl
- + ", defaultDisplaySizes: " + defaultDisplaySizes);
- return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ + ", defaultDisplaySizes: " + defaultDisplayInfo.defaultDisplaySizes);
+ return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(), rtl);
}
/**
@@ -445,7 +441,7 @@ public class WallpaperCropper {
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
adjustedSuggestedCrops.put(orientation,
- getCrop(displaySize, bitmapSize, suggestedCrops, rtl));
+ getCrop(displaySize, mDefaultDisplayInfo, bitmapSize, suggestedCrops, rtl));
}
}
@@ -455,7 +451,8 @@ public class WallpaperCropper {
int orientation = defaultDisplaySizes.keyAt(i);
if (result.contains(orientation)) continue;
Point displaySize = defaultDisplaySizes.valueAt(i);
- Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl);
+ Rect newCrop = getCrop(displaySize, mDefaultDisplayInfo, bitmapSize,
+ adjustedSuggestedCrops, rtl);
result.put(orientation, newCrop);
}
return result;
@@ -859,10 +856,14 @@ public class WallpaperCropper {
BitmapFactory.decodeFile(wallpaperFile.getAbsolutePath(), options);
wallpaperImageSize.set(options.outWidth, options.outHeight);
}
+ boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ Rect croppedImageBound = getCrop(displaySize, mDefaultDisplayInfo, wallpaperImageSize,
+ getRelativeCropHints(wallpaperData), isRtl);
- double maxDisplayToImageRatio = Math.max((double) displaySize.x / wallpaperImageSize.x,
- (double) displaySize.y / wallpaperImageSize.y);
- if (maxDisplayToImageRatio > 1.5) {
+ double maxDisplayToImageRatio = Math.max((double) displaySize.x / croppedImageBound.width(),
+ (double) displaySize.y / croppedImageBound.height());
+ if (maxDisplayToImageRatio > 1.3) {
return false;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index ba0262a8bd19..69f0ef7c430e 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -542,9 +542,11 @@ public class WallpaperDataParser {
// to support back compatibility in B&R, save the crops for one orientation in the
// legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries
int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet;
- if (mWallpaperDisplayHelper.isFoldable()) {
- int unfoldedOrientation = mWallpaperDisplayHelper
- .getUnfoldedOrientation(orientationToPutInLegacyCrop);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo =
+ mWallpaperDisplayHelper.getDefaultDisplayInfo();
+ if (defaultDisplayInfo.isFoldable) {
+ int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(
+ orientationToPutInLegacyCrop);
if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
orientationToPutInLegacyCrop = unfoldedOrientation;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java
new file mode 100644
index 000000000000..dabe91968338
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpaper;
+
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.getRotatedOrientation;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+
+import android.app.WallpaperManager;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+
+/** A data class for the default display attributes used in wallpaper related operations. */
+public final class WallpaperDefaultDisplayInfo {
+ /**
+ * A data class representing the screen orientations for a foldable device in the folded and
+ * unfolded states.
+ */
+ @VisibleForTesting
+ static final class FoldableOrientations {
+ @WallpaperManager.ScreenOrientation
+ public final int foldedOrientation;
+ @WallpaperManager.ScreenOrientation
+ public final int unfoldedOrientation;
+
+ FoldableOrientations(int foldedOrientation, int unfoldedOrientation) {
+ this.foldedOrientation = foldedOrientation;
+ this.unfoldedOrientation = unfoldedOrientation;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof FoldableOrientations that)) return false;
+ return foldedOrientation == that.foldedOrientation
+ && unfoldedOrientation == that.unfoldedOrientation;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(foldedOrientation, unfoldedOrientation);
+ }
+ }
+
+ public final SparseArray<Point> defaultDisplaySizes;
+ public final boolean isLargeScreen;
+ public final boolean isFoldable;
+ @VisibleForTesting
+ final List<FoldableOrientations> foldableOrientations;
+
+ public WallpaperDefaultDisplayInfo() {
+ this.defaultDisplaySizes = new SparseArray<>();
+ this.isLargeScreen = false;
+ this.isFoldable = false;
+ this.foldableOrientations = Collections.emptyList();
+ }
+
+ public WallpaperDefaultDisplayInfo(WindowManager windowManager, Resources resources) {
+ Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+ boolean isFoldable = resources.getIntArray(R.array.config_foldedDeviceStates).length > 0;
+ if (isFoldable) {
+ this.foldableOrientations = getFoldableOrientations(metrics);
+ } else {
+ this.foldableOrientations = Collections.emptyList();
+ }
+ this.defaultDisplaySizes = getDisplaySizes(metrics);
+ this.isLargeScreen = isLargeScreen(metrics);
+ this.isFoldable = isFoldable;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof WallpaperDefaultDisplayInfo that)) return false;
+ return isLargeScreen == that.isLargeScreen && isFoldable == that.isFoldable
+ && defaultDisplaySizes.contentEquals(that.defaultDisplaySizes)
+ && Objects.equals(foldableOrientations, that.foldableOrientations);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * Objects.hash(isLargeScreen, isFoldable, foldableOrientations)
+ + defaultDisplaySizes.contentHashCode();
+ }
+
+ /**
+ * Returns the folded orientation corresponds to the {@code unfoldedOrientation} found in
+ * {@link #foldableOrientations}. If not found, returns
+ * {@link WallpaperManager.ORIENTATION_UNKNOWN}.
+ */
+ public int getFoldedOrientation(int unfoldedOrientation) {
+ for (FoldableOrientations orientations : foldableOrientations) {
+ if (orientations.unfoldedOrientation == unfoldedOrientation) {
+ return orientations.foldedOrientation;
+ }
+ }
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * Returns the unfolded orientation corresponds to the {@code foldedOrientation} found in
+ * {@link #foldableOrientations}. If not found, returns
+ * {@link WallpaperManager.ORIENTATION_UNKNOWN}.
+ */
+ public int getUnfoldedOrientation(int foldedOrientation) {
+ for (FoldableOrientations orientations : foldableOrientations) {
+ if (orientations.foldedOrientation == foldedOrientation) {
+ return orientations.unfoldedOrientation;
+ }
+ }
+ return ORIENTATION_UNKNOWN;
+ }
+
+ private static SparseArray<Point> getDisplaySizes(Set<WindowMetrics> displayMetrics) {
+ SparseArray<Point> displaySizes = new SparseArray<>();
+ for (WindowMetrics metric : displayMetrics) {
+ Rect bounds = metric.getBounds();
+ Point displaySize = new Point(bounds.width(), bounds.height());
+ Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
+ for (Point point : List.of(displaySize, reversedDisplaySize)) {
+ int orientation = WallpaperManager.getOrientation(point);
+ // don't add an entry if there is already a larger display of the same orientation
+ Point display = displaySizes.get(orientation);
+ if (display == null || display.x * display.y < point.x * point.y) {
+ displaySizes.put(orientation, point);
+ }
+ }
+ }
+ return displaySizes;
+ }
+
+ private static boolean isLargeScreen(Set<WindowMetrics> displayMetrics) {
+ float smallestWidth = Float.MAX_VALUE;
+ for (WindowMetrics metric : displayMetrics) {
+ Rect bounds = metric.getBounds();
+ smallestWidth = Math.min(smallestWidth, bounds.width() / metric.getDensity());
+ }
+ return smallestWidth >= LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+ }
+
+ /**
+ * Determines all potential foldable orientations, populating {@code
+ * outFoldableOrientationPairs} with pairs of (folded orientation, unfolded orientation). If
+ * {@code defaultDisplayMetrics} isn't for foldable, {@code outFoldableOrientationPairs} will
+ * not be populated.
+ */
+ private static List<FoldableOrientations> getFoldableOrientations(
+ Set<WindowMetrics> defaultDisplayMetrics) {
+ if (defaultDisplayMetrics.size() != 2) {
+ return Collections.emptyList();
+ }
+ List<FoldableOrientations> foldableOrientations = new ArrayList<>();
+ float surface = 0;
+ int firstOrientation = -1;
+ for (WindowMetrics metric : defaultDisplayMetrics) {
+ Rect bounds = metric.getBounds();
+ Point displaySize = new Point(bounds.width(), bounds.height());
+
+ int orientation = WallpaperManager.getOrientation(displaySize);
+ float newSurface = displaySize.x * displaySize.y
+ / (metric.getDensity() * metric.getDensity());
+ if (surface <= 0) {
+ surface = newSurface;
+ firstOrientation = orientation;
+ } else {
+ FoldableOrientations orientations = (newSurface > surface)
+ ? new FoldableOrientations(firstOrientation, orientation)
+ : new FoldableOrientations(orientation, firstOrientation);
+ FoldableOrientations rotatedOrientations = new FoldableOrientations(
+ getRotatedOrientation(orientations.foldedOrientation),
+ getRotatedOrientation(orientations.unfoldedOrientation));
+ foldableOrientations.add(orientations);
+ foldableOrientations.add(rotatedOrientations);
+ }
+ }
+ return Collections.unmodifiableList(foldableOrientations);
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 3636f5aa8f27..bff5fc9c49f3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -16,31 +16,25 @@
package com.android.server.wallpaper;
-import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
-import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.window.flags.Flags.multiCrop;
import android.app.WallpaperManager;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Debug;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
-import android.view.WindowMetrics;
import com.android.server.wm.WindowManagerInternal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -59,65 +53,25 @@ class WallpaperDisplayHelper {
}
private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
- private static final float LARGE_SCREEN_MIN_DP = 600f;
private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
private final DisplayManager mDisplayManager;
private final WindowManagerInternal mWindowManagerInternal;
- private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>();
- // related orientations pairs for foldable (folded orientation, unfolded orientation)
- private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
-
- private final boolean mIsFoldable;
- private boolean mIsLargeScreen = false;
+ private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo;
WallpaperDisplayHelper(
DisplayManager displayManager,
WindowManager windowManager,
WindowManagerInternal windowManagerInternal,
- boolean isFoldable) {
+ Resources resources) {
mDisplayManager = displayManager;
mWindowManagerInternal = windowManagerInternal;
- mIsFoldable = isFoldable;
- if (!multiCrop()) return;
- Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
- boolean populateOrientationPairs = isFoldable && metrics.size() == 2;
- float surface = 0;
- int firstOrientation = -1;
- for (WindowMetrics metric: metrics) {
- Rect bounds = metric.getBounds();
- Point displaySize = new Point(bounds.width(), bounds.height());
- Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
- for (Point point : List.of(displaySize, reversedDisplaySize)) {
- int orientation = WallpaperManager.getOrientation(point);
- // don't add an entry if there is already a larger display of the same orientation
- Point display = mDefaultDisplaySizes.get(orientation);
- if (display == null || display.x * display.y < point.x * point.y) {
- mDefaultDisplaySizes.put(orientation, point);
- }
- }
-
- mIsLargeScreen |= (displaySize.x / metric.getDensity() >= LARGE_SCREEN_MIN_DP);
-
- if (populateOrientationPairs) {
- int orientation = WallpaperManager.getOrientation(displaySize);
- float newSurface = displaySize.x * displaySize.y
- / (metric.getDensity() * metric.getDensity());
- if (surface <= 0) {
- surface = newSurface;
- firstOrientation = orientation;
- } else {
- Pair<Integer, Integer> pair = (newSurface > surface)
- ? new Pair<>(firstOrientation, orientation)
- : new Pair<>(orientation, firstOrientation);
- Pair<Integer, Integer> rotatedPair = new Pair<>(
- getRotatedOrientation(pair.first), getRotatedOrientation(pair.second));
- mFoldableOrientationPairs.add(pair);
- mFoldableOrientationPairs.add(rotatedPair);
- }
- }
+ if (!multiCrop()) {
+ mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo();
+ return;
}
+ mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources);
}
DisplayData getDisplayDataOrCreate(int displayId) {
@@ -203,51 +157,21 @@ class WallpaperDisplayHelper {
}
SparseArray<Point> getDefaultDisplaySizes() {
- return mDefaultDisplaySizes;
+ return mDefaultDisplayInfo.defaultDisplaySizes;
}
/** Return the number of pixel of the largest dimension of the default display */
int getDefaultDisplayLargestDimension() {
+ SparseArray<Point> defaultDisplaySizes = mDefaultDisplayInfo.defaultDisplaySizes;
int result = -1;
- for (int i = 0; i < mDefaultDisplaySizes.size(); i++) {
- Point size = mDefaultDisplaySizes.valueAt(i);
+ for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+ Point size = defaultDisplaySizes.valueAt(i);
result = Math.max(result, Math.max(size.x, size.y));
}
return result;
}
- boolean isFoldable() {
- return mIsFoldable;
- }
-
- /**
- * Return true if any of the screens of the default display is considered large (DP >= 600)
- */
- boolean isLargeScreen() {
- return mIsLargeScreen;
- }
-
- /**
- * If a given orientation corresponds to an unfolded orientation on foldable, return the
- * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
- * device is not a foldable.
- */
- int getFoldedOrientation(int orientation) {
- for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
- if (pair.second.equals(orientation)) return pair.first;
- }
- return ORIENTATION_UNKNOWN;
- }
-
- /**
- * If a given orientation corresponds to a folded orientation on foldable, return the
- * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
- * device is not a foldable.
- */
- int getUnfoldedOrientation(int orientation) {
- for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
- if (pair.first.equals(orientation)) return pair.second;
- }
- return ORIENTATION_UNKNOWN;
+ public WallpaperDefaultDisplayInfo getDefaultDisplayInfo() {
+ return mDefaultDisplayInfo;
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index d620e98d3437..e7da33d50b27 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1173,12 +1173,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return;
}
- final ComponentName wpService = mWallpaper.getComponent();
// The broadcast of package update could be delayed after service disconnected. Try
// to re-bind the service for 10 seconds.
mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND;
- if (bindWallpaperComponentLocked(
- wpService, true, false, mWallpaper, null)) {
+ boolean success;
+ if (liveWallpaperContentHandling()) {
+ success = bindWallpaperDescriptionLocked(
+ mWallpaper.getDescription(), /* force= */ true,
+ /* fromUser= */ false, mWallpaper, /* reply= */ null);
+ } else {
+ success = bindWallpaperComponentLocked(mWallpaper.getComponent(), /* force= */
+ true, /* fromUser= */false, mWallpaper, /* reply= */ null);
+ }
+ if (success) {
mWallpaper.connection.scheduleTimeoutLocked();
} else if (SystemClock.uptimeMillis() - mWallpaper.lastDiedTime
< WALLPAPER_RECONNECT_TIMEOUT_MS) {
@@ -1189,7 +1196,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Timeout
Slog.w(TAG, "Reverting to built-in wallpaper!");
clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
- final String flattened = wpService.flattenToString();
+ final String flattened = mWallpaper.getComponent().flattenToString();
EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
flattened.substring(0, Math.min(flattened.length(),
MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
@@ -1659,12 +1666,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
displayManager.registerDisplayListener(mDisplayListener, null /* handler */);
WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- boolean isFoldable = mContext.getResources()
- .getIntArray(R.array.config_foldedDeviceStates).length > 0;
mWallpaperDisplayHelper = new WallpaperDisplayHelper(
- displayManager, windowManager, mWindowManagerInternal, isFoldable);
+ displayManager, windowManager, mWindowManagerInternal, mContext.getResources());
mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
- mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop);
mActivityManager = mContext.getSystemService(ActivityManager.class);
if (mContext.getResources().getBoolean(
@@ -2503,9 +2507,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
List<Rect> result = new ArrayList<>();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
== View.LAYOUT_DIRECTION_RTL;
+ WallpaperDefaultDisplayInfo defaultDisplayInfo =
+ mWallpaperDisplayHelper.getDefaultDisplayInfo();
for (Point displaySize : displaySizes) {
- result.add(mWallpaperCropper.getCrop(
- displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
+ result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo,
+ croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
}
if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result);
return result;
@@ -2541,8 +2547,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
List<Rect> result = new ArrayList<>();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
== View.LAYOUT_DIRECTION_RTL;
+ WallpaperDefaultDisplayInfo defaultDisplayInfo =
+ mWallpaperDisplayHelper.getDefaultDisplayInfo();
for (Point displaySize : displaySizes) {
- result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl));
+ result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo, bitmapSize,
+ defaultCrops, rtl));
}
return result;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 58534b95bdde..f243d4fa825a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -153,7 +153,7 @@ final class AccessibilityController {
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
if (dc != null) {
final Display display = dc.getDisplay();
- if (display != null && display.getType() != Display.TYPE_OVERLAY) {
+ if (display != null) {
final DisplayMagnifier magnifier = new DisplayMagnifier(
mService, dc, display, callbacks);
magnifier.notifyImeWindowVisibilityChanged(
@@ -294,19 +294,6 @@ final class AccessibilityController {
}
}
- void onAppWindowTransition(int displayId, int transition) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition",
- FLAGS_MAGNIFICATION_CALLBACK,
- "displayId=" + displayId + "; transition=" + transition);
- }
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier != null) {
- displayMagnifier.onAppWindowTransition(displayId, transition);
- }
- // Not relevant for the window observer.
- }
-
void onWMTransition(int displayId, @TransitionType int type, @TransitionFlags int flags) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(TAG + ".onWMTransition",
@@ -670,34 +657,6 @@ final class AccessibilityController {
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
- void onAppWindowTransition(int displayId, int transition) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".onAppWindowTransition",
- FLAGS_MAGNIFICATION_CALLBACK,
- "displayId=" + displayId + "; transition=" + transition);
- }
- if (DEBUG_WINDOW_TRANSITIONS) {
- Slog.i(LOG_TAG, "Window transition: "
- + AppTransition.appTransitionOldToString(transition)
- + " displayId: " + displayId);
- }
- final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
- if (!isMagnifierActivated) {
- return;
- }
- switch (transition) {
- case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_TO_FRONT:
- case WindowManager.TRANSIT_OLD_WALLPAPER_OPEN:
- case WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE:
- case WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN: {
- mUserContextChangedNotifier.onAppWindowTransition(transition);
- }
- }
- }
-
void onWMTransition(int displayId, @TransitionType int type, @TransitionFlags int flags) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".onWMTransition",
@@ -734,7 +693,7 @@ final class AccessibilityController {
}
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: "
- + AppTransition.appTransitionOldToString(transition)
+ + WindowManager.transitTypeToString(transition)
+ " displayId: " + windowState.getDisplayId());
}
final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7ce52b101824..7da4beb95114 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1113,11 +1113,11 @@ class ActivityClientController extends IActivityClientController.Stub {
false /* fromClient */);
}
+ final EnterPipRequestedItem item = new EnterPipRequestedItem(r.token);
try {
- final EnterPipRequestedItem item = new EnterPipRequestedItem(r.token);
- mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
- return true;
- } catch (Exception e) {
+ return mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Failed to send enter pip requested item: "
+ r.intent.getComponent(), e);
return false;
@@ -1129,10 +1129,11 @@ class ActivityClientController extends IActivityClientController.Stub {
*/
void onPictureInPictureUiStateChanged(@NonNull ActivityRecord r,
PictureInPictureUiState pipState) {
+ final PipStateTransactionItem item = new PipStateTransactionItem(r.token, pipState);
try {
- final PipStateTransactionItem item = new PipStateTransactionItem(r.token, pipState);
mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
- } catch (Exception e) {
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Failed to send pip state transaction item: "
+ r.intent.getComponent(), e);
}
@@ -1510,9 +1511,6 @@ class ActivityClientController extends IActivityClientController.Stub {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null && r.isState(RESUMED, PAUSING)) {
- r.mDisplayContent.mAppTransition.overridePendingAppTransition(
- packageName, enterAnim, exitAnim, backgroundColor, null, null,
- r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
enterAnim, 0 /* changeResId */, exitAnim,
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a94183849bc5..e2b47b92f232 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -112,7 +112,6 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.apphibernation.AppHibernationService;
-import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@@ -807,14 +806,8 @@ class ActivityMetricsLogger {
}
final Task otherTask = otherInfo.mLastLaunchedActivity.getTask();
// The adjacent task is the split root in which activities are started
- final boolean isDescendantOfAdjacent;
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks(
- otherTask::isDescendantOf);
- } else {
- isDescendantOfAdjacent = otherTask.isDescendantOf(
- launchedSplitRootTask.getAdjacentTask());
- }
+ final boolean isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks(
+ otherTask::isDescendantOf);
if (isDescendantOfAdjacent) {
if (DEBUG_METRICS) {
Slog.i(TAG, "Found adjacent tasks t1=" + launchedActivityTask.mTaskId
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d452d76db18d..df00fa195f03 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -253,6 +253,7 @@ import android.annotation.Size;
import android.app.Activity;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
+import android.app.IApplicationThread;
import android.app.IScreenCaptureObserver;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
@@ -624,7 +625,7 @@ final class ActivityRecord extends WindowToken {
@VisibleForTesting
final TaskFragment.ConfigOverrideHint mResolveConfigHint;
- private final boolean mOptOutEdgeToEdge;
+ final boolean mOptOutEdgeToEdge;
private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
@@ -672,9 +673,6 @@ final class ActivityRecord extends WindowToken {
// TODO(b/317000737): Replace it with visibility states lookup.
int mTransitionChangeFlags;
- /** Whether we need to setup the animation to animate only within the letterbox. */
- private boolean mNeedsLetterboxedAnimation;
-
/**
* @see #currentLaunchCanTurnScreenOn()
*/
@@ -1351,15 +1349,16 @@ final class ActivityRecord extends WindowToken {
this, displayId);
return;
}
- try {
- ProtoLog.v(WM_DEBUG_SWITCH, "Reporting activity moved to "
- + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
- config);
+ ProtoLog.v(WM_DEBUG_SWITCH, "Reporting activity moved to "
+ + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
+ config);
- final MoveToDisplayItem item =
- new MoveToDisplayItem(token, displayId, config, activityWindowInfo);
+ final MoveToDisplayItem item =
+ new MoveToDisplayItem(token, displayId, config, activityWindowInfo);
+ try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// If process died, whatever.
}
}
@@ -1371,14 +1370,15 @@ final class ActivityRecord extends WindowToken {
+ "update - client not running, activityRecord=%s", this);
return;
}
- try {
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
- + "config: %s", this, config);
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
+ + "config: %s", this, config);
- final ActivityConfigurationChangeItem item =
- new ActivityConfigurationChangeItem(token, config, activityWindowInfo);
+ final ActivityConfigurationChangeItem item =
+ new ActivityConfigurationChangeItem(token, config, activityWindowInfo);
+ try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// If process died, whatever.
}
}
@@ -1393,19 +1393,18 @@ final class ActivityRecord extends WindowToken {
if (onTop) {
app.addToPendingTop();
}
- try {
- ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
- this, onTop);
+ ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
+ this, onTop);
- final TopResumedActivityChangeItem item =
- new TopResumedActivityChangeItem(token, onTop);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
+ final TopResumedActivityChangeItem item = new TopResumedActivityChangeItem(token, onTop);
+ try {
+ return mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// If process died, whatever.
Slog.w(TAG, "Failed to send top-resumed=" + onTop + " to " + this, e);
return false;
}
- return true;
}
void updateMultiWindowMode() {
@@ -1717,6 +1716,7 @@ final class ActivityRecord extends WindowToken {
}
mAppCompatController.getLetterboxPolicy().onMovedToDisplay(mDisplayContent.getDisplayId());
+ mAppCompatController.getDisplayCompatModePolicy().onMovedToDisplay();
}
void layoutLetterboxIfNeeded(WindowState winHint) {
@@ -2604,14 +2604,21 @@ final class ActivityRecord extends WindowToken {
removeStartingWindow();
return;
}
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
+ final TransferSplashScreenViewStateItem item =
+ new TransferSplashScreenViewStateItem(token, parcelable, windowAnimationLeash);
+ boolean isSuccessful;
try {
- mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
- final TransferSplashScreenViewStateItem item =
- new TransferSplashScreenViewStateItem(token, parcelable, windowAnimationLeash);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- scheduleTransferSplashScreenTimeout();
- } catch (Exception e) {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
+ scheduleTransferSplashScreenTimeout();
+ } else {
mStartingWindow.cancelAnimation();
parcelable.clearIfNeeded();
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
@@ -3795,19 +3802,10 @@ final class ActivityRecord extends WindowToken {
final TaskFragment taskFragment = getTaskFragment();
if (next != null && taskFragment != null && taskFragment.isEmbedded()) {
final TaskFragment organized = taskFragment.getOrganizedTaskFragment();
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- delayRemoval = organized != null
- && organized.topRunningActivity() == null
- && organized.isDelayLastActivityRemoval()
- && organized.forOtherAdjacentTaskFragments(next::isDescendantOf);
- } else {
- final TaskFragment adjacent =
- organized != null ? organized.getAdjacentTaskFragment() : null;
- if (adjacent != null && next.isDescendantOf(adjacent)
- && organized.topRunningActivity() == null) {
- delayRemoval = organized.isDelayLastActivityRemoval();
- }
- }
+ delayRemoval = organized != null
+ && organized.topRunningActivity() == null
+ && organized.isDelayLastActivityRemoval()
+ && organized.forOtherAdjacentTaskFragments(next::isDescendantOf);
}
// isNextNotYetVisible is to check if the next activity is invisible, or it has been
@@ -3957,11 +3955,23 @@ final class ActivityRecord extends WindowToken {
boolean skipDestroy = false;
- try {
- if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
+ if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
+ boolean isSuccessful;
+ final IApplicationThread client = app.getThread();
+ if (client == null) {
+ Slog.w(TAG_WM, "Failed to schedule DestroyActivityItem because client is inactive");
+ isSuccessful = false;
+ } else {
final DestroyActivityItem item = new DestroyActivityItem(token, finishing);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- } catch (Exception e) {
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ client, item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
+ }
+ }
+ if (!isSuccessful) {
// We can just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
if (finishing) {
@@ -4769,11 +4779,6 @@ final class ActivityRecord extends WindowToken {
}
// Make sure the embedded adjacent can also be shown.
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final ActivityRecord adjacentActivity = taskFragment.getAdjacentTaskFragment()
- .getTopNonFinishingActivity();
- return canShowWhenLocked(adjacentActivity);
- }
final boolean hasAdjacentNotAllowToShow = taskFragment.forOtherAdjacentTaskFragments(
adjacentTF -> !canShowWhenLocked(adjacentTF.getTopNonFinishingActivity()));
return !hasAdjacentNotAllowToShow;
@@ -4884,13 +4889,17 @@ final class ActivityRecord extends WindowToken {
}
if (isState(RESUMED) && attachedToProcess()) {
+ final ArrayList<ResultInfo> list = new ArrayList<>();
+ list.add(new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
+ final ActivityResultItem item = new ActivityResultItem(token, list);
try {
- final ArrayList<ResultInfo> list = new ArrayList<>();
- list.add(new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
- final ActivityResultItem item = new ActivityResultItem(token, list);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- return;
- } catch (Exception e) {
+ final boolean isSuccessful = mAtmService.getLifecycleManager()
+ .scheduleTransactionItem(app.getThread(), item);
+ if (isSuccessful) {
+ return;
+ }
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending result to " + this, e);
}
}
@@ -4917,6 +4926,7 @@ final class ActivityRecord extends WindowToken {
app.getThread(), activityResultItem);
}
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending result to " + this, e);
}
// We return here to ensure that result for media projection setup is not stored as a
@@ -4989,7 +4999,6 @@ final class ActivityRecord extends WindowToken {
}
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
- boolean unsent = true;
final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
// We want to immediately deliver the intent to the activity if:
@@ -4998,25 +5007,26 @@ final class ActivityRecord extends WindowToken {
// - The device is sleeping and it is the top activity behind the lock screen (b/6700897).
if ((mState == RESUMED || mState == PAUSED || isTopActivityWhileSleeping)
&& attachedToProcess()) {
+ final ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
+ ar.add(rintent);
+ // Making sure the client state is RESUMED after transaction completed and doing
+ // so only if activity is currently RESUMED. Otherwise, client may have extra
+ // life-cycle calls to RESUMED (and PAUSED later).
+ final NewIntentItem item = new NewIntentItem(token, ar, mState == RESUMED /* resume */);
try {
- ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
- ar.add(rintent);
- // Making sure the client state is RESUMED after transaction completed and doing
- // so only if activity is currently RESUMED. Otherwise, client may have extra
- // life-cycle calls to RESUMED (and PAUSED later).
- final NewIntentItem item =
- new NewIntentItem(token, ar, mState == RESUMED /* resume */);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- unsent = false;
+ final boolean isSuccessful = mAtmService.getLifecycleManager()
+ .scheduleTransactionItem(app.getThread(), item);
+ if (isSuccessful) {
+ return;
+ }
} catch (RemoteException e) {
- Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
- } catch (NullPointerException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
}
}
- if (unsent) {
- addNewIntentLocked(rintent);
- }
+
+ // Didn't send.
+ addNewIntentLocked(rintent);
}
void updateOptionsLocked(ActivityOptions options) {
@@ -5580,16 +5590,12 @@ final class ActivityRecord extends WindowToken {
commitVisibility(visible, performLayout, false /* fromTransition */);
}
- void setNeedsLetterboxedAnimation(boolean needsLetterboxedAnimation) {
- mNeedsLetterboxedAnimation = needsLetterboxedAnimation;
- }
-
- boolean isNeedsLetterboxedAnimation() {
- return mNeedsLetterboxedAnimation;
- }
-
- boolean isInLetterboxAnimation() {
- return mNeedsLetterboxedAnimation && isAnimating();
+ /**
+ * Sets whether safe region bounds are needed for the Activity. This is called from
+ * {@link ActivityStarter} after the source record is created.
+ */
+ void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
+ mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(needsSafeRegionBounds);
}
/** Updates draw state and shows drawn windows. */
@@ -6044,11 +6050,12 @@ final class ActivityRecord extends WindowToken {
setState(PAUSING, "makeActiveIfNeeded");
EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this),
shortComponentName, "userLeaving=false", "make-active");
+ final PauseActivityItem item = new PauseActivityItem(token, finishing,
+ false /* userLeaving */, false /* dontReport */, mAutoEnteringPip);
try {
- final PauseActivityItem item = new PauseActivityItem(token, finishing,
- false /* userLeaving */, false /* dontReport */, mAutoEnteringPip);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- } catch (Exception e) {
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
} else if (shouldStartActivity()) {
@@ -6057,10 +6064,11 @@ final class ActivityRecord extends WindowToken {
}
setState(STARTED, "makeActiveIfNeeded");
+ final StartActivityItem item = new StartActivityItem(token, takeSceneTransitionInfo());
try {
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- new StartActivityItem(token, takeSceneTransitionInfo()));
- } catch (Exception e) {
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
}
// The activity may be waiting for stop, but that is no longer appropriate if we are
@@ -6343,23 +6351,29 @@ final class ActivityRecord extends WindowToken {
return;
}
resumeKeyDispatchingLocked();
- try {
- ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);
-
- setState(STOPPING, "stopIfPossible");
- if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Stopping:" + this);
- }
- EventLogTags.writeWmStopActivity(
- mUserId, System.identityHashCode(this), shortComponentName);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- new StopActivityItem(token));
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);
- mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
- } catch (Exception e) {
+ setState(STOPPING, "stopIfPossible");
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Stopping:" + this);
+ }
+ EventLogTags.writeWmStopActivity(
+ mUserId, System.identityHashCode(this), shortComponentName);
+ final StopActivityItem item = new StopActivityItem(token);
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// Maybe just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
Slog.w(TAG, "Exception thrown during pause", e);
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
+ mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
+ } else {
// Just in case, assume it to be stopped.
mAppStopped = true;
mStoppedTime = SystemClock.uptimeMillis();
@@ -7253,10 +7267,6 @@ final class ActivityRecord extends WindowToken {
.setParent(getAnimationLeashParent())
.setName(getSurfaceControl() + " - animation-bounds")
.setCallsite("ActivityRecord.createAnimationBoundsLayer");
- if (mNeedsLetterboxedAnimation) {
- // Needs to be an effect layer to support rounded corners
- builder.setEffectLayer();
- }
final SurfaceControl boundsLayer = builder.build();
t.show(boundsLayer);
return boundsLayer;
@@ -7274,11 +7284,6 @@ final class ActivityRecord extends WindowToken {
@Override
public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
- if (mNeedsLetterboxedAnimation) {
- updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
- mNeedsAnimationBoundsLayer = true;
- }
-
// If the animation needs to be cropped then an animation bounds layer is created as a
// child of the root pinned task or animation layer. The leash is then reparented to this
// new layer.
@@ -7291,17 +7296,6 @@ final class ActivityRecord extends WindowToken {
t.setLayer(leash, 0);
t.setLayer(mAnimationBoundsLayer, getLastLayer());
- if (mNeedsLetterboxedAnimation) {
- final int cornerRadius = mAppCompatController.getLetterboxPolicy()
- .getRoundedCornersRadius(findMainWindow());
-
- final Rect letterboxInnerBounds = new Rect();
- getLetterboxInnerBounds(letterboxInnerBounds);
-
- t.setCornerRadius(mAnimationBoundsLayer, cornerRadius)
- .setCrop(mAnimationBoundsLayer, letterboxInnerBounds);
- }
-
// Reparent leash to animation bounds layer.
t.reparent(leash, mAnimationBoundsLayer);
}
@@ -7355,10 +7349,6 @@ final class ActivityRecord extends WindowToken {
}
mNeedsAnimationBoundsLayer = false;
- if (mNeedsLetterboxedAnimation) {
- mNeedsLetterboxedAnimation = false;
- updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
- }
}
@Override
@@ -7771,9 +7761,11 @@ final class ActivityRecord extends WindowToken {
final AppCompatAspectRatioPolicy aspectRatioPolicy =
mAppCompatController.getAspectRatioPolicy();
aspectRatioPolicy.reset();
+ final AppCompatSafeRegionPolicy safeRegionPolicy =
+ mAppCompatController.getSafeRegionPolicy();
mAppCompatController.getLetterboxPolicy().resetFixedOrientationLetterboxEligibility();
mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
- isFixedRotationTransforming());
+ isFixedRotationTransforming(), safeRegionPolicy.getLatestSafeRegionBounds());
// Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
// different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -7819,6 +7811,13 @@ final class ActivityRecord extends WindowToken {
}
}
+ // If activity can be letterboxed due to a safe region only, use the safe region bounds
+ // as the resolved bounds. We ignore cases where the letterboxing can happen due to other
+ // app compat conditions and a safe region since the safe region app compat is sandboxed
+ // earlier in TaskFragment.ConfigOverrideHint.resolveTmpOverrides.
+ mAppCompatController.getSafeRegionPolicy().resolveSafeRegionBoundsConfigurationIfNeeded(
+ resolvedConfig, newParentConfiguration);
+
if (isFixedOrientationLetterboxAllowed
|| scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
// In fullscreen, can be letterboxed for aspect ratio.
@@ -7998,7 +7997,7 @@ final class ActivityRecord extends WindowToken {
mAppCompatController.getSizeCompatModePolicy();
final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect parentBounds = mResolveConfigHint.mParentBoundsOverride;
final float screenResolvedBoundsWidth = screenResolvedBounds.width();
final float parentAppBoundsWidth = parentAppBounds.width();
final boolean isImmersiveMode = isImmersiveMode(parentBounds);
@@ -8185,7 +8184,7 @@ final class ActivityRecord extends WindowToken {
* in this method.
*/
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
- final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+ final Rect parentBounds = mResolveConfigHint.mParentBoundsOverride;
final Rect stableBounds = new Rect();
final Rect outNonDecorBounds = mTmpBounds;
// If orientation is respected when insets are applied, then stableBounds will be empty.
@@ -8711,9 +8710,11 @@ final class ActivityRecord extends WindowToken {
}
// Figure out how to handle the changes between the configurations.
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=0x%s, "
- + "handles=0x%s, mLastReportedConfiguration=%s", info.name,
- Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=%s, "
+ + "handles=%s, not-handles=%s, mLastReportedConfiguration=%s", info.name,
+ Configuration.configurationDiffToString(changes),
+ Configuration.configurationDiffToString(info.getRealConfigChanged()),
+ Configuration.configurationDiffToString(changes & ~(info.getRealConfigChanged())),
mLastReportedConfiguration);
if (shouldRelaunchLocked(changes, mTmpConfig)) {
@@ -8926,29 +8927,34 @@ final class ActivityRecord extends WindowToken {
task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
}
+ ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
+ (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
+ final ClientTransactionItem callbackItem = new ActivityRelaunchItem(token,
+ pendingResults, pendingNewIntents, configChangeFlags,
+ new MergedConfiguration(getProcessGlobalConfiguration(),
+ getMergedOverrideConfiguration()),
+ preserveWindow, getActivityWindowInfo());
+ final ActivityLifecycleItem lifecycleItem;
+ if (andResume) {
+ lifecycleItem = new ResumeActivityItem(token, isTransitionForward(),
+ shouldSendCompatFakeFocus());
+ } else {
+ lifecycleItem = new PauseActivityItem(token);
+ }
+ boolean isSuccessful;
try {
- ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
- (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
- final ClientTransactionItem callbackItem = new ActivityRelaunchItem(token,
- pendingResults, pendingNewIntents, configChangeFlags,
- new MergedConfiguration(getProcessGlobalConfiguration(),
- getMergedOverrideConfiguration()),
- preserveWindow, getActivityWindowInfo());
- final ActivityLifecycleItem lifecycleItem;
- if (andResume) {
- lifecycleItem = new ResumeActivityItem(token, isTransitionForward(),
- shouldSendCompatFakeFocus());
- } else {
- lifecycleItem = new PauseActivityItem(token);
- }
- mAtmService.getLifecycleManager().scheduleTransactionItems(
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItems(
app.getThread(), callbackItem, lifecycleItem);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ Slog.w(TAG, "Failed to relaunch " + this + ": " + e);
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
startRelaunching();
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// request resume if this activity is currently resumed, which implies we aren't
// sleeping.
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to relaunch " + this + ": " + e);
}
if (andResume) {
@@ -8978,6 +8984,7 @@ final class ActivityRecord extends WindowToken {
// Reset the existing override configuration so it can be updated according to the latest
// configuration.
mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
+ mAppCompatController.getDisplayCompatModePolicy().onProcessRestarted();
if (!attachedToProcess()) {
return;
@@ -9028,10 +9035,11 @@ final class ActivityRecord extends WindowToken {
private void scheduleStopForRestartProcess() {
// The process will be killed until the activity reports stopped with saved state (see
// {@link ActivityTaskManagerService.activityStopped}).
+ final StopActivityItem item = new StopActivityItem(token);
try {
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- new StopActivityItem(token));
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown during restart " + this, e);
}
mTaskSupervisor.scheduleRestartTimeout(this);
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 25e38b307b5e..8fe603cad46b 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -88,17 +88,22 @@ class ActivityRefresher {
new RefreshCallbackItem(activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ boolean isSuccessful;
try {
- activity.mAtmService.getLifecycleManager().scheduleTransactionItems(
+ isSuccessful = activity.mAtmService.getLifecycleManager().scheduleTransactionItems(
activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+ } catch (RemoteException e) {
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
mHandler.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
onActivityRefreshed(activity);
}
}, REFRESH_CALLBACK_TIMEOUT_MS);
- } catch (RemoteException e) {
- activity.mAppCompatController.getCameraOverrides()
- .setIsRefreshRequested(false);
+ } else {
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
+
}
}
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 21628341ea62..0f1939bfbb49 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -34,7 +34,6 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.window.flags.Flags;
import java.io.File;
import java.io.PrintWriter;
@@ -107,8 +106,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
&& !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go
setSnapshotEnabled(snapshotEnabled);
mSnapshotPersistQueue = persistQueue;
- mPersistInfoProvider = createPersistInfoProvider(service,
- Environment::getDataSystemCeDirectory);
+ mPersistInfoProvider = createPersistInfoProvider(service);
mPersister = new TaskSnapshotPersister(
persistQueue,
mPersistInfoProvider,
@@ -117,6 +115,11 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
initialize(new ActivitySnapshotCache());
}
+ @VisibleForTesting
+ PersistInfoProvider createPersistInfoProvider(WindowManagerService service) {
+ return createPersistInfoProvider(service, Environment::getDataSystemCeDirectory);
+ }
+
@Override
protected float initSnapshotScale() {
final float config = mService.mContext.getResources().getFloat(
@@ -528,26 +531,6 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
final int currentIndex = currTF.asTask() != null
? currentTask.mChildren.indexOf(currentActivity)
: currentTask.mChildren.indexOf(currTF);
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final int prevAdjacentIndex = currentTask.mChildren.indexOf(
- prevTF.getAdjacentTaskFragment());
- if (prevAdjacentIndex > currentIndex) {
- // PrevAdjacentTF already above currentActivity
- return;
- }
- // Add both the one below, and its adjacent.
- if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
- result.add(initPrev);
- }
- final ActivityRecord prevAdjacentActivity = prevTF.getAdjacentTaskFragment()
- .getTopMostActivity();
- if (prevAdjacentActivity != null && (!inTransition
- || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
- result.add(prevAdjacentActivity);
- }
- return;
- }
-
final boolean hasAdjacentAboveCurrent = prevTF.forOtherAdjacentTaskFragments(
prevAdjacentTF -> {
final int prevAdjacentIndex = currentTask.mChildren.indexOf(prevAdjacentTF);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 3f24da9d89f2..51025d204b46 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -60,6 +60,7 @@ import com.android.server.wm.ActivityStarter.DefaultFactory;
import com.android.server.wm.ActivityStarter.Factory;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -97,6 +98,9 @@ public class ActivityStartController {
/** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
private boolean mInExecution = false;
+ /** The {@link TaskDisplayArea}s that are currently starting home activity. */
+ private ArrayList<TaskDisplayArea> mHomeLaunchingTaskDisplayAreas = new ArrayList<>();
+
/**
* TODO(b/64750076): Capture information necessary for dump and
* {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -162,6 +166,11 @@ public class ActivityStartController {
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason,
TaskDisplayArea taskDisplayArea) {
+ if (mHomeLaunchingTaskDisplayAreas.contains(taskDisplayArea)) {
+ Slog.e(TAG, "Abort starting home on " + taskDisplayArea + " recursively.");
+ return;
+ }
+
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
if (!ActivityRecord.isResolverActivity(aInfo.name)) {
@@ -186,13 +195,18 @@ public class ActivityStartController {
mSupervisor.endDeferResume();
}
- mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
- .setOutActivity(tmpOutRecord)
- .setCallingUid(0)
- .setActivityInfo(aInfo)
- .setActivityOptions(options.toBundle(),
- Binder.getCallingPid(), Binder.getCallingUid())
- .execute();
+ try {
+ mHomeLaunchingTaskDisplayAreas.add(taskDisplayArea);
+ mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
+ .setOutActivity(tmpOutRecord)
+ .setCallingUid(0)
+ .setActivityInfo(aInfo)
+ .setActivityOptions(options.toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid())
+ .execute();
+ } finally {
+ mHomeLaunchingTaskDisplayAreas.remove(taskDisplayArea);
+ }
mLastHomeActivityStartRecord = tmpOutRecord[0];
if (rootHomeTask.mInResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
@@ -479,9 +493,9 @@ public class ActivityStartController {
}
} catch (SecurityException securityException) {
ActivityStarter.logAndThrowExceptionForIntentRedirect(mService.mContext,
- "Creator URI Grant Caused Exception.", intent, creatorUid,
- creatorPackage, filterCallingUid, callingPackage,
- securityException);
+ ActivityStarter.INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION,
+ intent, creatorUid, creatorPackage, filterCallingUid,
+ callingPackage, securityException);
}
}
if ((aInfo.applicationInfo.privateFlags
@@ -720,6 +734,12 @@ public class ActivityStartController {
}
}
+ if (!mHomeLaunchingTaskDisplayAreas.isEmpty()) {
+ dumped = true;
+ pw.print(prefix);
+ pw.println("mHomeLaunchingTaskDisplayAreas:" + mHomeLaunchingTaskDisplayAreas);
+ }
+
if (!dumped) {
pw.print(prefix);
pw.println("(nothing)");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bdde5fe9dcb5..92f51bed419f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -59,14 +59,13 @@ import static android.security.Flags.preventIntentRedirectAbortOrThrowException;
import static android.security.Flags.preventIntentRedirectShowToast;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_AVOID_MOVE_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.internal.util.FrameworkStatsLog.INTENT_REDIRECT_BLOCKED;
import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
@@ -142,6 +141,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.UiThread;
import com.android.server.am.ActivityManagerService.IntentCreatorToken;
import com.android.server.am.PendingIntentRecord;
@@ -625,7 +625,7 @@ class ActivityStarter {
if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
!= 0) {
logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
- "Unparceled intent does not have a creator token set.", intent,
+ ActivityStarter.INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN, intent,
intentCreatorUid, intentCreatorPackage, resolvedCallingUid,
resolvedCallingPackage, null);
}
@@ -661,9 +661,9 @@ class ActivityStarter {
}
} catch (SecurityException securityException) {
logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
- "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
- intentCreatorPackage, resolvedCallingUid,
- resolvedCallingPackage, securityException);
+ ActivityStarter.INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION,
+ intent, intentCreatorUid, intentCreatorPackage,
+ resolvedCallingUid, resolvedCallingPackage, securityException);
}
}
} else {
@@ -685,9 +685,9 @@ class ActivityStarter {
}
} catch (SecurityException securityException) {
logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
- "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
- intentCreatorPackage, resolvedCallingUid,
- resolvedCallingPackage, securityException);
+ ActivityStarter.INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION,
+ intent, intentCreatorUid, intentCreatorPackage,
+ resolvedCallingUid, resolvedCallingPackage, securityException);
}
}
}
@@ -1111,8 +1111,11 @@ class ActivityStarter {
if (sourceRecord != null) {
if (requestCode >= 0 && !sourceRecord.finishing) {
resultRecord = sourceRecord;
+ request.logMessage.append(" (rr=");
+ } else {
+ request.logMessage.append(" (sr=");
}
- request.logMessage.append(" (sr=" + System.identityHashCode(sourceRecord) + ")");
+ request.logMessage.append(System.identityHashCode(sourceRecord) + ")");
}
}
@@ -1263,27 +1266,27 @@ class ActivityStarter {
request.ignoreTargetSecurity, inTask != null, null, resultRecord,
resultRootTask)) {
abort = logAndAbortForIntentRedirect(mService.mContext,
- "Creator checkStartAnyActivityPermission Caused abortion.",
+ ActivityStarter.INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION,
intent, intentCreatorUid, intentCreatorPackage, callingUid,
callingPackage);
}
} catch (SecurityException e) {
logAndThrowExceptionForIntentRedirect(mService.mContext,
- "Creator checkStartAnyActivityPermission Caused Exception.",
+ ActivityStarter.INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION,
intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage,
e);
}
if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid,
0, resolvedType, aInfo.applicationInfo)) {
abort = logAndAbortForIntentRedirect(mService.mContext,
- "Creator IntentFirewall.checkStartActivity Caused abortion.",
+ ActivityStarter.INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY,
intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
}
if (!mService.getPermissionPolicyInternal().checkStartActivity(intent,
intentCreatorUid, intentCreatorPackage)) {
abort = logAndAbortForIntentRedirect(mService.mContext,
- "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
+ ActivityStarter.INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY,
intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
}
}
@@ -1992,6 +1995,11 @@ class ActivityStarter {
}
}
+ if (com.android.window.flags.Flags.earlyLaunchHint()) {
+ mRootWindowContainer.startPowerModeLaunchIfNeeded(
+ false /* forceSend */, mStartActivity);
+ }
+
if (mTargetRootTask == null) {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
mOptions);
@@ -2064,8 +2072,10 @@ class ActivityStarter {
mStartActivity.getTaskFragment().clearLastPausedActivity();
- mRootWindowContainer.startPowerModeLaunchIfNeeded(
- false /* forceSend */, mStartActivity);
+ if (!com.android.window.flags.Flags.earlyLaunchHint()) {
+ mRootWindowContainer.startPowerModeLaunchIfNeeded(
+ false /* forceSend */, mStartActivity);
+ }
final boolean isTaskSwitch = startedTask != prevTopTask;
mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
@@ -2193,6 +2203,9 @@ class ActivityStarter {
? mLaunchParams.mPreferredTaskDisplayArea
: mRootWindowContainer.getDefaultTaskDisplayArea();
mPreferredWindowingMode = mLaunchParams.mWindowingMode;
+ if (mLaunchParams.mNeedsSafeRegionBounds != null) {
+ r.setNeedsSafeRegionBounds(mLaunchParams.mNeedsSafeRegionBounds);
+ }
}
private TaskDisplayArea computeSuggestedLaunchDisplayArea(
@@ -2551,11 +2564,6 @@ class ActivityStarter {
if (actuallyMoved) {
// Only record if the activity actually moved.
mMovedToTopActivity = act;
- if (mNoAnimation) {
- act.mDisplayContent.prepareAppTransition(TRANSIT_NONE);
- } else {
- act.mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
- }
}
act.updateOptionsLocked(mOptions);
deliverNewIntent(act, intentGrants);
@@ -3626,13 +3634,41 @@ class ActivityStarter {
pw.println(mInTaskFragment);
}
+ /**
+ * Error codes for intent redirect.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"INTENT_REDIRECT_"}, value = {
+ INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN,
+ INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION,
+ INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION,
+ INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION,
+ INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY,
+ INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface IntentRedirectErrorCode {
+ }
+
+ /**
+ * Error codes for intent redirect issues
+ */
+ static final int INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN = 1;
+ static final int INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION = 2;
+ static final int INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION = 3;
+ static final int INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION = 4;
+ static final int INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY = 5;
+ static final int INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY = 6;
+
static void logAndThrowExceptionForIntentRedirect(@NonNull Context context,
- @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+ @IntentRedirectErrorCode int errorCode, @NonNull Intent intent, int intentCreatorUid,
@Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage,
@Nullable SecurityException originalException) {
- String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+ String msg = getIntentRedirectPreventedLogMessage(errorCode, intent, intentCreatorUid,
intentCreatorPackage, callingUid, callingPackage);
Slog.wtf(TAG, msg);
+ FrameworkStatsLog.write(INTENT_REDIRECT_BLOCKED, intentCreatorUid, callingUid, errorCode);
if (preventIntentRedirectShowToast()) {
UiThread.getHandler().post(
() -> Toast.makeText(context,
@@ -3646,12 +3682,13 @@ class ActivityStarter {
}
private static boolean logAndAbortForIntentRedirect(@NonNull Context context,
- @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+ @IntentRedirectErrorCode int errorCode, @NonNull Intent intent, int intentCreatorUid,
@Nullable String intentCreatorPackage, int callingUid,
@Nullable String callingPackage) {
- String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+ String msg = getIntentRedirectPreventedLogMessage(errorCode, intent, intentCreatorUid,
intentCreatorPackage, callingUid, callingPackage);
Slog.wtf(TAG, msg);
+ FrameworkStatsLog.write(INTENT_REDIRECT_BLOCKED, intentCreatorUid, callingUid, errorCode);
if (preventIntentRedirectShowToast()) {
UiThread.getHandler().post(
() -> Toast.makeText(context,
@@ -3662,11 +3699,38 @@ class ActivityStarter {
ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid);
}
- private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
+ private static String getIntentRedirectPreventedLogMessage(
+ @IntentRedirectErrorCode int errorCode,
@NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
int callingUid, @Nullable String callingPackage) {
+ String message = getIntentRedirectErrorMessageFromCode(errorCode);
return "[IntentRedirect Hardening] " + message + " intentCreatorUid: " + intentCreatorUid
+ "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
+ "; callingPackage: " + callingPackage + "; intent: " + intent;
}
+
+ private static String getIntentRedirectErrorMessageFromCode(
+ @IntentRedirectErrorCode int errorCode) {
+ return switch (errorCode) {
+ case INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN ->
+ "INTENT_REDIRECT_EXCEPTION_MISSING_OR_INVALID_TOKEN"
+ + " (Unparceled intent does not have a creator token set, throw exception.)";
+ case INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION ->
+ "INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION"
+ + " (Creator URI permission grant throw exception.)";
+ case INTENT_REDIRECT_EXCEPTION_START_ANY_ACTIVITY_PERMISSION ->
+ "INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION"
+ + " (Creator checkStartAnyActivityPermission, throw exception)";
+ case INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION ->
+ "INTENT_REDIRECT_ABORT_START_ANY_ACTIVITY_PERMISSION"
+ + " (Creator checkStartAnyActivityPermission, abort)";
+ case INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY ->
+ "INTENT_REDIRECT_ABORT_INTENT_FIREWALL_START_ACTIVITY"
+ + " (Creator IntentFirewall.checkStartActivity, abort)";
+ case INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY ->
+ "INTENT_REDIRECT_ABORT_PERMISSION_POLICY_START_ACTIVITY"
+ + " (Creator PermissionPolicyService.checkStartActivity, abort)";
+ default -> "Unknown error code: " + errorCode;
+ };
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index c45f7e8a7da2..b7ef1057388c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -36,6 +36,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.ACTION_VIEW;
@@ -125,6 +126,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
@@ -163,7 +165,6 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1045,16 +1046,23 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// transaction.
mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread());
}
+ final boolean isSuccessful;
try {
- mService.getLifecycleManager().scheduleTransactionItems(
+ isSuccessful = mService.getLifecycleManager().scheduleTransactionItems(
proc.getThread(),
// Immediately dispatch the transaction, so that if it fails, the server can
// restart the process and retry now.
true /* shouldDispatchImmediately */,
launchActivityItem, lifecycleItem);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
return e;
}
+ if (com.android.window.flags.Flags.cleanupDispatchPendingTransactionsRemoteException()
+ && !isSuccessful) {
+ return new DeadObjectException("Failed to dispatch the ClientTransaction to dead"
+ + " process. See earlier log for more details.");
+ }
if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
// If the seq is increased, there should be something changed (e.g. registered
@@ -1627,16 +1635,19 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
- private void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea,
+ @VisibleForTesting
+ void moveHomeRootTaskToFrontIfNeeded(int flags, TaskDisplayArea taskDisplayArea,
String reason) {
final Task focusedRootTask = taskDisplayArea.getFocusedRootTask();
if ((taskDisplayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& (flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0)
- || (focusedRootTask != null && focusedRootTask.isActivityTypeRecents())) {
+ || (focusedRootTask != null && focusedRootTask.isActivityTypeRecents()
+ && focusedRootTask.getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
// We move root home task to front when we are on a fullscreen display area and
// caller has requested the home activity to move with it. Or the previous root task
- // is recents.
+ // is recents and we are not on multi-window mode.
+
taskDisplayArea.moveHomeRootTaskToFront(reason);
}
}
@@ -2823,6 +2834,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
"startActivityFromRecents: Task " + taskId + " not found.");
}
+
+ if (task.getRootTask() != null
+ && task.getRootTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ // Don't move home forward if task is in multi window mode
+ moveHomeTaskForward = false;
+ }
+
if (moveHomeTaskForward) {
// We always want to return to the home activity instead of the recents
// activity from whatever is started from the recents activity, so move
@@ -2983,17 +3001,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (child.asTaskFragment() != null
&& child.asTaskFragment().hasAdjacentTaskFragment()) {
- final boolean isAnyTranslucent;
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final TaskFragment.AdjacentSet set =
- child.asTaskFragment().getAdjacentTaskFragments();
- isAnyTranslucent = set.forAllTaskFragments(
- tf -> !isOpaque(tf), null);
- } else {
- final TaskFragment adjacent = child.asTaskFragment()
- .getAdjacentTaskFragment();
- isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent);
- }
+ final boolean isAnyTranslucent = !isOpaque(child)
+ || child.asTaskFragment().forOtherAdjacentTaskFragments(
+ tf -> !isOpaque(tf));
if (!isAnyTranslucent) {
// This task fragment and all its adjacent task fragments are opaque,
// consider it opaque even if it doesn't fill its parent.
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 3535a96d9c45..d6f058a34367 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -240,7 +240,7 @@ class AppCompatAspectRatioPolicy {
final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
final Rect parentAppBounds =
mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect parentBounds = mActivityRecord.mResolveConfigHint.mParentBoundsOverride;
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
// Use tmp bounds to calculate aspect ratio so we can know whether the activity should
// use restricted size (resolved bounds may be the requested override bounds).
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 48f08e945a59..28f9a33d65d3 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -33,6 +33,8 @@ class AppCompatController {
@NonNull
private final AppCompatAspectRatioPolicy mAspectRatioPolicy;
@NonNull
+ private final AppCompatSafeRegionPolicy mSafeRegionPolicy;
+ @NonNull
private final AppCompatReachabilityPolicy mReachabilityPolicy;
@NonNull
private final DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy;
@@ -46,6 +48,8 @@ class AppCompatController {
private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy;
@NonNull
private final AppCompatSandboxingPolicy mSandboxingPolicy;
+ @NonNull
+ private final AppCompatDisplayCompatModePolicy mDisplayCompatModePolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -60,6 +64,7 @@ class AppCompatController {
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
mTransparentPolicy, mAppCompatOverrides);
+ mSafeRegionPolicy = new AppCompatSafeRegionPolicy(activityRecord, packageManager);
mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
wmService.mAppCompatConfiguration);
mLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
@@ -69,6 +74,7 @@ class AppCompatController {
mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
mAppCompatOverrides);
mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord);
+ mDisplayCompatModePolicy = new AppCompatDisplayCompatModePolicy();
}
@NonNull
@@ -87,6 +93,11 @@ class AppCompatController {
}
@NonNull
+ AppCompatSafeRegionPolicy getSafeRegionPolicy() {
+ return mSafeRegionPolicy;
+ }
+
+ @NonNull
DesktopAppCompatAspectRatioPolicy getDesktopAspectRatioPolicy() {
return mDesktopAspectRatioPolicy;
}
@@ -151,10 +162,15 @@ class AppCompatController {
return mSandboxingPolicy;
}
+ @NonNull
+ AppCompatDisplayCompatModePolicy getDisplayCompatModePolicy() {
+ return mDisplayCompatModePolicy;
+ }
+
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
getTransparentPolicy().dump(pw, prefix);
getLetterboxPolicy().dump(pw, prefix);
getSizeCompatModePolicy().dump(pw, prefix);
+ getSafeRegionPolicy().dump(pw, prefix);
}
-
}
diff --git a/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java
new file mode 100644
index 000000000000..acf51707c894
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Encapsulate app-compat logic for multi-display environments.
+ */
+class AppCompatDisplayCompatModePolicy {
+
+ private boolean mIsRestartMenuEnabledForDisplayMove;
+
+ boolean isRestartMenuEnabledForDisplayMove() {
+ return Flags.enableRestartMenuForConnectedDisplays() && mIsRestartMenuEnabledForDisplayMove;
+ }
+
+ void onMovedToDisplay() {
+ mIsRestartMenuEnabledForDisplayMove = true;
+ }
+
+ void onProcessRestarted() {
+ mIsRestartMenuEnabledForDisplayMove = false;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 6873270366ee..27511b2f1668 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -217,7 +217,7 @@ class AppCompatLetterboxPolicy {
}
final boolean shouldShowLetterboxUi =
- (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+ (mActivityRecord.isVisible()
|| mActivityRecord.isVisibleRequested())
&& mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
@@ -360,8 +360,7 @@ class AppCompatLetterboxPolicy {
.mAppCompatController.getReachabilityPolicy();
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
- reachabilityPolicy, letterboxOverrides,
- this::getLetterboxParentSurface);
+ reachabilityPolicy, letterboxOverrides);
mActivityRecord.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
}
@@ -469,15 +468,6 @@ class AppCompatLetterboxPolicy {
public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect);
}
-
- @Nullable
- private SurfaceControl getLetterboxParentSurface() {
- if (mActivityRecord.isInLetterboxAnimation()) {
- return mActivityRecord.getTask().getSurfaceControl();
- }
- return mActivityRecord.getSurfaceControl();
- }
-
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
index 83d7cb7bb960..e5b61db2ef68 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
@@ -36,12 +36,7 @@ class AppCompatLetterboxUtils {
outLetterboxPosition.set(0, 0);
return;
}
- if (activity.isInLetterboxAnimation()) {
- // In this case we attach the letterbox to the task instead of the activity.
- activity.getTask().getPosition(outLetterboxPosition);
- } else {
- activity.getPosition(outLetterboxPosition);
- }
+ activity.getPosition(outLetterboxPosition);
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
index 8165638d4bda..92b91464312e 100644
--- a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
+++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
@@ -58,7 +58,7 @@ class AppCompatRoundedCorners {
@VisibleForTesting
@Nullable
Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) {
- if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+ if (!requiresRoundedCorners(mainWindow)) {
// We don't want corner radius on the window.
// In the case the ActivityRecord requires a letterboxed animation we never want
// rounded corners on the window because rounded corners are applied at the
diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
new file mode 100644
index 000000000000..959609309da1
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Encapsulate app compat policy logic related to a safe region.
+ */
+class AppCompatSafeRegionPolicy {
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ final PackageManager mPackageManager;
+ // Whether the Activity needs to be in the safe region bounds.
+ private boolean mNeedsSafeRegionBounds = false;
+ // Denotes the latest safe region bounds. Can be empty if the activity or the ancestors do
+ // not have any safe region bounds.
+ @NonNull
+ private final Rect mLatestSafeRegionBounds = new Rect();
+ // Whether the activity has allowed safe region letterboxing. This can be set through the
+ // manifest and the default value is true.
+ @NonNull
+ private final BooleanSupplier mAllowSafeRegionLetterboxing;
+
+ AppCompatSafeRegionPolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull PackageManager packageManager) {
+ mActivityRecord = activityRecord;
+ mPackageManager = packageManager;
+ mAllowSafeRegionLetterboxing = AppCompatUtils.asLazy(() -> {
+ // Application level property.
+ if (allowSafeRegionLetterboxing(packageManager)) {
+ return true;
+ }
+ // Activity level property.
+ try {
+ return packageManager.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ mActivityRecord.mActivityComponent.getPackageName(),
+ mActivityRecord.mActivityComponent.getClassName(),
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return true;
+ }
+ });
+ }
+
+ private boolean allowSafeRegionLetterboxing(PackageManager pm) {
+ try {
+ return pm.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ mActivityRecord.packageName,
+ /* className */ null,
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return true;
+ }
+ }
+
+ /**
+ * Computes the latest safe region bounds in
+ * {@link ActivityRecord#resolveOverrideConfiguration(Configuration)} since the activity has not
+ * been attached to the parent container when the ActivityRecord is instantiated. Note that the
+ * latest safe region bounds will be empty if activity has not allowed safe region letterboxing.
+ *
+ * @return latest safe region bounds as set on an ancestor window container.
+ */
+ public Rect getLatestSafeRegionBounds() {
+ if (!allowSafeRegionLetterboxing()) {
+ mLatestSafeRegionBounds.setEmpty();
+ return null;
+ }
+ // Get the latest safe region bounds since the bounds could have changed
+ final Rect latestSafeRegionBounds = mActivityRecord.getSafeRegionBounds();
+ if (latestSafeRegionBounds != null) {
+ mLatestSafeRegionBounds.set(latestSafeRegionBounds);
+ } else {
+ mLatestSafeRegionBounds.setEmpty();
+ }
+ return latestSafeRegionBounds;
+ }
+
+ /**
+ * Computes bounds when letterboxing is required only for the safe region bounds if needed.
+ */
+ public void resolveSafeRegionBoundsConfigurationIfNeeded(@NonNull Configuration resolvedConfig,
+ @NonNull Configuration newParentConfig) {
+ if (mLatestSafeRegionBounds.isEmpty()) {
+ return;
+ }
+ // If activity can not be letterboxed for a safe region only or it has not been attached
+ // to a WindowContainer yet.
+ if (!isLetterboxedForSafeRegionOnlyAllowed() || mActivityRecord.getParent() == null) {
+ return;
+ }
+ resolvedConfig.windowConfiguration.setBounds(mLatestSafeRegionBounds);
+ mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfig);
+ }
+
+ /**
+ * Safe region bounds can either be applied along with size compat, fixed orientation or
+ * aspect ratio conditions by sandboxing them to the safe region bounds. Or it can be applied
+ * independently when no other letterboxing condition is triggered. This method helps detecting
+ * the latter case.
+ *
+ * @return {@code true} if this application or activity has allowed safe region letterboxing and
+ * can be letterboxed only due to the safe region being set on the current or ancestor window
+ * container.
+ */
+ boolean isLetterboxedForSafeRegionOnlyAllowed() {
+ return !mActivityRecord.areBoundsLetterboxed() && getNeedsSafeRegionBounds()
+ && getLatestSafeRegionBounds() != null;
+ }
+
+ /**
+ * Set {@code true} if this activity needs to be within the safe region bounds, else false.
+ */
+ public void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
+ mNeedsSafeRegionBounds = needsSafeRegionBounds;
+ }
+
+ /**
+ * @return {@code true} if this activity needs to be within the safe region bounds.
+ */
+ public boolean getNeedsSafeRegionBounds() {
+ return mNeedsSafeRegionBounds;
+ }
+
+ /** @see android.view.WindowManager#PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING */
+ boolean allowSafeRegionLetterboxing() {
+ return mAllowSafeRegionLetterboxing.getAsBoolean();
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ if (mNeedsSafeRegionBounds) {
+ pw.println(prefix + " mNeedsSafeRegionBounds=true");
+ }
+ if (isLetterboxedForSafeRegionOnlyAllowed()) {
+ pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true");
+ }
+ if (!allowSafeRegionLetterboxing()) {
+ pw.println(prefix + " allowSafeRegionLetterboxing=false");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index 2cfa242bc5fe..0f1e36d70db2 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static com.android.window.flags.Flags.enableSizeCompatModeImprovementsForConnectedDisplays;
import static com.android.server.wm.AppCompatUtils.isInDesktopMode;
import android.annotation.NonNull;
@@ -357,6 +358,14 @@ class AppCompatSizeCompatModePolicy {
// relatively fixed.
overrideConfig.colorMode = fullConfig.colorMode;
overrideConfig.densityDpi = fullConfig.densityDpi;
+ if (enableSizeCompatModeImprovementsForConnectedDisplays()) {
+ overrideConfig.touchscreen = fullConfig.touchscreen;
+ overrideConfig.navigation = fullConfig.navigation;
+ overrideConfig.keyboard = fullConfig.keyboard;
+ overrideConfig.keyboardHidden = fullConfig.keyboardHidden;
+ overrideConfig.hardKeyboardHidden = fullConfig.hardKeyboardHidden;
+ overrideConfig.navigationHidden = fullConfig.navigationHidden;
+ }
// The smallest screen width is the short side of screen bounds. Because the bounds
// and density won't be changed, smallestScreenWidthDp is also fixed.
overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 146044008b3f..f872286726c3 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -161,6 +161,9 @@ final class AppCompatUtils {
top.mAppCompatController.getLetterboxOverrides()
.isLetterboxEducationEnabled());
+ appCompatTaskInfo.setRestartMenuEnabledForDisplayMove(top.mAppCompatController
+ .getDisplayCompatModePolicy().isRestartMenuEnabledForDisplayMove());
+
final AppCompatAspectRatioOverrides aspectRatioOverrides =
top.mAppCompatController.getAspectRatioOverrides();
appCompatTaskInfo.setUserFullscreenOverrideEnabled(
@@ -213,6 +216,7 @@ final class AppCompatUtils {
AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
.getDesktopAspectRatioPolicy().hasMinAspectRatioOverride(task));
+ appCompatTaskInfo.setOptOutEdgeToEdge(top.mOptOutEdgeToEdge);
}
/**
@@ -238,6 +242,10 @@ final class AppCompatUtils {
if (aspectRatioPolicy.isLetterboxedForAspectRatioOnly()) {
return "ASPECT_RATIO";
}
+ if (activityRecord.mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed()) {
+ return "SAFE_REGION";
+ }
return "UNKNOWN_REASON";
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
deleted file mode 100644
index 12d4a210400c..000000000000
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ /dev/null
@@ -1,1587 +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.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.LayoutParams;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_UNSET;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
-import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
-
-import android.annotation.ColorInt;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.hardware.HardwareBuffer;
-import android.os.Binder;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RemoteAnimationAdapter;
-import android.view.WindowManager.TransitionFlags;
-import android.view.WindowManager.TransitionOldType;
-import android.view.WindowManager.TransitionType;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.common.LogLevel;
-import com.android.internal.util.DumpUtils.Dump;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.wm.ActivityRecord.CustomAppTransition;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-// State management of app transitions. When we are preparing for a
-// transition, mNextAppTransition will be the kind of transition to
-// perform or TRANSIT_NONE if we are not waiting. If we are waiting,
-// mOpeningApps and mClosingApps are the lists of tokens that will be
-// made visible or hidden at the next transition.
-public class AppTransition implements Dump {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransition" : TAG_WM;
-
- static final int DEFAULT_APP_TRANSITION_DURATION = 336;
-
- /**
- * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
- * involved, to make it more understandable.
- */
- private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
- static final int MAX_APP_TRANSITION_DURATION = 3 * 1000; // 3 secs.
-
- private final Context mContext;
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
-
- @VisibleForTesting
- final TransitionAnimation mTransitionAnimation;
-
- private @TransitionFlags int mNextAppTransitionFlags = 0;
- private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
- private @TransitionOldType int mLastUsedAppTransition = TRANSIT_OLD_UNSET;
- private String mLastOpeningApp;
- private String mLastClosingApp;
- private String mLastChangingApp;
-
- private static final int NEXT_TRANSIT_TYPE_NONE = 0;
- private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
- private static final int NEXT_TRANSIT_TYPE_SCALE_UP = 2;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP = 3;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
- private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
- private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8;
-
- /**
- * Refers to the transition to activity started by using {@link
- * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
- * }.
- */
- private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9;
- private static final int NEXT_TRANSIT_TYPE_REMOTE = 10;
-
- private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
- private boolean mNextAppTransitionOverrideRequested;
-
- private String mNextAppTransitionPackage;
- // Used for thumbnail transitions. True if we're scaling up, false if scaling down
- private boolean mNextAppTransitionScaleUp;
- private IRemoteCallback mNextAppTransitionCallback;
- private IRemoteCallback mNextAppTransitionFutureCallback;
- private IRemoteCallback mAnimationFinishedCallback;
- private int mNextAppTransitionEnter;
- private int mNextAppTransitionExit;
- private @ColorInt int mNextAppTransitionBackgroundColor;
- private int mNextAppTransitionInPlace;
- private boolean mNextAppTransitionIsSync;
-
- // Keyed by WindowContainer hashCode.
- private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
- = new SparseArray<>();
- private IAppTransitionAnimationSpecsFuture mNextAppTransitionAnimationsSpecsFuture;
- private boolean mNextAppTransitionAnimationsSpecsPending;
- private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
-
- private final Rect mTmpRect = new Rect();
-
- private final static int APP_STATE_IDLE = 0;
- private final static int APP_STATE_READY = 1;
- private final static int APP_STATE_RUNNING = 2;
- private final static int APP_STATE_TIMEOUT = 3;
- private int mAppTransitionState = APP_STATE_IDLE;
-
- private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
- private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
-
- private final int mDefaultWindowAnimationStyleResId;
- private boolean mOverrideTaskTransition;
-
- final Handler mHandler;
- final Runnable mHandleAppTransitionTimeoutRunnable = () -> handleAppTransitionTimeout();
-
- AppTransition(Context context, WindowManagerService service, DisplayContent displayContent) {
- mContext = context;
- mService = service;
- mHandler = new Handler(service.mH.getLooper());
- mDisplayContent = displayContent;
- mTransitionAnimation = new TransitionAnimation(
- context, ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG), TAG);
-
- final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- mDefaultWindowAnimationStyleResId = windowStyle.getResourceId(
- com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- windowStyle.recycle();
- }
-
- boolean isTransitionSet() {
- return !mNextAppTransitionRequests.isEmpty();
- }
-
- boolean isUnoccluding() {
- return mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_UNOCCLUDE);
- }
-
- boolean transferFrom(AppTransition other) {
- mNextAppTransitionRequests.addAll(other.mNextAppTransitionRequests);
- return prepare();
- }
-
- void setLastAppTransition(@TransitionOldType int transit, ActivityRecord openingApp,
- ActivityRecord closingApp, ActivityRecord changingApp) {
- mLastUsedAppTransition = transit;
- mLastOpeningApp = "" + openingApp;
- mLastClosingApp = "" + closingApp;
- mLastChangingApp = "" + changingApp;
- }
-
- boolean isReady() {
- return mAppTransitionState == APP_STATE_READY
- || mAppTransitionState == APP_STATE_TIMEOUT;
- }
-
- void setReady() {
- setAppTransitionState(APP_STATE_READY);
- fetchAppTransitionSpecsFromFuture();
- }
-
- boolean isRunning() {
- return mAppTransitionState == APP_STATE_RUNNING;
- }
-
- void setIdle() {
- setAppTransitionState(APP_STATE_IDLE);
- }
-
- boolean isIdle() {
- return mAppTransitionState == APP_STATE_IDLE;
- }
-
- boolean isTimeout() {
- return mAppTransitionState == APP_STATE_TIMEOUT;
- }
-
- void setTimeout() {
- setAppTransitionState(APP_STATE_TIMEOUT);
- }
-
- /**
- * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
- */
- @Nullable
- Animation getNextAppRequestedAnimation(boolean enter) {
- final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
- mNextAppTransitionPackage,
- enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
- if (mNextAppTransitionBackgroundColor != 0 && a != null) {
- a.setBackdropColor(mNextAppTransitionBackgroundColor);
- }
- return a;
- }
-
- /**
- * Gets the animation background color overridden by app via
- * {@link #overridePendingAppTransition}.
- */
- @ColorInt int getNextAppTransitionBackgroundColor() {
- return mNextAppTransitionBackgroundColor;
- }
-
- @VisibleForTesting
- boolean isNextAppTransitionOverrideRequested() {
- return mNextAppTransitionOverrideRequested;
- }
-
- HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- if (spec == null) {
- spec = mDefaultNextAppTransitionAnimationSpec;
- }
- return spec != null ? spec.buffer : null;
- }
-
- /** Returns whether the next thumbnail transition is aspect scaled up. */
- boolean isNextThumbnailTransitionAspectScaled() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- }
-
- /** Returns whether the next thumbnail transition is scaling up. */
- boolean isNextThumbnailTransitionScaleUp() {
- return mNextAppTransitionScaleUp;
- }
-
- boolean isNextAppTransitionThumbnailUp() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP;
- }
-
- boolean isNextAppTransitionThumbnailDown() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- }
-
- boolean isNextAppTransitionOpenCrossProfileApps() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
- }
-
- /**
- * @return true if and only if we are currently fetching app transition specs from the future
- * passed into {@link #overridePendingAppTransitionMultiThumbFuture}
- */
- boolean isFetchingAppTransitionsSpecs() {
- return mNextAppTransitionAnimationsSpecsPending;
- }
-
- private boolean prepare() {
- if (!isRunning()) {
- setAppTransitionState(APP_STATE_IDLE);
- notifyAppTransitionPendingLocked();
- return true;
- }
- return false;
- }
-
- /**
- * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
- * layout pass needs to be done
- */
- int goodToGo(@TransitionOldType int transit, ActivityRecord topOpeningApp) {
- mNextAppTransitionFlags = 0;
- mNextAppTransitionRequests.clear();
- setAppTransitionState(APP_STATE_RUNNING);
- final WindowContainer wc =
- topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
- final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
-
- int redoLayout = notifyAppTransitionStartingLocked(
- topOpeningAnim != null
- ? topOpeningAnim.getStatusBarTransitionsStartTime()
- : SystemClock.uptimeMillis(),
- AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
-
- if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
- && topOpeningAnim != null) {
- if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()) {
- final NavBarFadeAnimationController controller =
- new NavBarFadeAnimationController(mDisplayContent);
- // For remote animation case, the nav bar fades out and in is controlled by the
- // remote side. For non-remote animation case, we play the fade out/in animation
- // here. We play the nav bar fade-out animation when the app transition animation
- // starts and play the fade-in animation sequentially once the fade-out is finished.
- controller.fadeOutAndInSequentially(topOpeningAnim.getDurationHint(),
- null /* fadeOutParent */, topOpeningApp.getSurfaceControl());
- }
- }
- return redoLayout;
- }
-
- void clear() {
- clear(true /* clearAppOverride */);
- }
-
- private void clear(boolean clearAppOverride) {
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
- mNextAppTransitionOverrideRequested = false;
- mNextAppTransitionAnimationsSpecs.clear();
- mNextAppTransitionAnimationsSpecsFuture = null;
- mDefaultNextAppTransitionAnimationSpec = null;
- mAnimationFinishedCallback = null;
- mOverrideTaskTransition = false;
- mNextAppTransitionIsSync = false;
- if (clearAppOverride) {
- mNextAppTransitionPackage = null;
- mNextAppTransitionEnter = 0;
- mNextAppTransitionExit = 0;
- mNextAppTransitionBackgroundColor = 0;
- }
- }
-
- void freeze() {
- final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
- TRANSIT_KEYGUARD_GOING_AWAY);
-
- mNextAppTransitionRequests.clear();
- clear();
- setReady();
- notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled);
- }
-
- private void setAppTransitionState(int state) {
- mAppTransitionState = state;
- updateBooster();
- }
-
- /**
- * Updates whether we currently boost wm locked sections and the animation thread. We want to
- * boost the priorities to a more important value whenever an app transition is going to happen
- * soon or an app transition is running.
- */
- void updateBooster() {
- WindowManagerService.sThreadPriorityBooster.setAppTransitionRunning(needsBoosting());
- }
-
- private boolean needsBoosting() {
- return !mNextAppTransitionRequests.isEmpty()
- || mAppTransitionState == APP_STATE_READY
- || mAppTransitionState == APP_STATE_RUNNING;
- }
-
- void registerListenerLocked(AppTransitionListener listener) {
- mListeners.add(listener);
- }
-
- void unregisterListener(AppTransitionListener listener) {
- mListeners.remove(listener);
- }
-
- public void notifyAppTransitionFinishedLocked(IBinder token) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionFinishedLocked(token);
- }
- }
-
- private void notifyAppTransitionPendingLocked() {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionPendingLocked();
- }
- }
-
- private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled);
- }
- }
-
- private void notifyAppTransitionTimeoutLocked() {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionTimeoutLocked();
- }
- }
-
- private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
- int redoLayout = 0;
- for (int i = 0; i < mListeners.size(); i++) {
- redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(
- statusBarAnimationStartTime, statusBarAnimationDuration);
- }
- return redoLayout;
- }
-
- @VisibleForTesting
- int getDefaultWindowAnimationStyleResId() {
- return mDefaultWindowAnimationStyleResId;
- }
-
- /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
- @VisibleForTesting
- int getAnimationStyleResId(@NonNull LayoutParams lp) {
- return mTransitionAnimation.getAnimationStyleResId(lp);
- }
-
- @VisibleForTesting
- @Nullable
- Animation loadAnimationSafely(Context context, int resId) {
- return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
- }
-
- private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
- int animAttr = 0;
- switch (transit) {
- case TRANSIT_OLD_ACTIVITY_OPEN:
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN:
- animAttr = enter
- ? WindowAnimation_activityOpenEnterAnimation
- : WindowAnimation_activityOpenExitAnimation;
- break;
- case TRANSIT_OLD_ACTIVITY_CLOSE:
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE:
- animAttr = enter
- ? WindowAnimation_activityCloseEnterAnimation
- : WindowAnimation_activityCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_OPEN:
- animAttr = enter
- ? WindowAnimation_taskOpenEnterAnimation
- : WindowAnimation_taskOpenExitAnimation;
- break;
- case TRANSIT_OLD_TASK_CLOSE:
- animAttr = enter
- ? WindowAnimation_taskCloseEnterAnimation
- : WindowAnimation_taskCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_TO_FRONT:
- animAttr = enter
- ? WindowAnimation_taskToFrontEnterAnimation
- : WindowAnimation_taskToFrontExitAnimation;
- break;
- case TRANSIT_OLD_TASK_TO_BACK:
- animAttr = enter
- ? WindowAnimation_taskToBackEnterAnimation
- : WindowAnimation_taskToBackExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_OPEN:
- animAttr = enter
- ? WindowAnimation_wallpaperOpenEnterAnimation
- : WindowAnimation_wallpaperOpenExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_CLOSE:
- animAttr = enter
- ? WindowAnimation_wallpaperCloseEnterAnimation
- : WindowAnimation_wallpaperCloseExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_INTRA_OPEN:
- animAttr = enter
- ? WindowAnimation_wallpaperIntraOpenEnterAnimation
- : WindowAnimation_wallpaperIntraOpenExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE:
- animAttr = enter
- ? WindowAnimation_wallpaperIntraCloseEnterAnimation
- : WindowAnimation_wallpaperIntraCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_OPEN_BEHIND:
- animAttr = enter
- ? WindowAnimation_launchTaskBehindSourceAnimation
- : WindowAnimation_launchTaskBehindTargetAnimation;
- break;
- // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
- // need new TaskFragment transition.
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- animAttr = enter
- ? WindowAnimation_activityOpenEnterAnimation
- : WindowAnimation_activityOpenExitAnimation;
- break;
- // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
- // need new TaskFragment transition.
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- animAttr = enter
- ? WindowAnimation_activityCloseEnterAnimation
- : WindowAnimation_activityCloseExitAnimation;
- break;
- case TRANSIT_OLD_DREAM_ACTIVITY_OPEN:
- animAttr = enter
- ? WindowAnimation_dreamActivityOpenEnterAnimation
- : WindowAnimation_dreamActivityOpenExitAnimation;
- break;
- case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE:
- animAttr = enter
- ? 0
- : WindowAnimation_dreamActivityCloseExitAnimation;
- break;
- }
-
- return animAttr;
- }
-
- @Nullable
- Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
- return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit);
- }
-
- private void getDefaultNextAppTransitionStartRect(Rect rect) {
- if (mDefaultNextAppTransitionAnimationSpec == null ||
- mDefaultNextAppTransitionAnimationSpec.rect == null) {
- Slog.e(TAG, "Starting rect for app requested, but none available", new Throwable());
- rect.setEmpty();
- } else {
- rect.set(mDefaultNextAppTransitionAnimationSpec.rect);
- }
- }
-
- private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height,
- HardwareBuffer buffer) {
- mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
- buffer, new Rect(left, top, left + width, top + height));
- }
-
- /**
- * Creates an overlay with a background color and a thumbnail for the cross profile apps
- * animation.
- */
- HardwareBuffer createCrossProfileAppsThumbnail(
- Drawable thumbnailDrawable, Rect frame) {
- return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
- }
-
- Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
- return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect);
- }
-
- /**
- * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
- * when a thumbnail is specified with the pending animation override.
- */
- Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
- HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) {
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect,
- contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null,
- mNextAppTransitionScaleUp);
- }
-
- private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
- Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
- final float sourceWidth = sourceFrame.width();
- final float sourceHeight = sourceFrame.height();
- final float destWidth = destFrame.width();
- final float destHeight = destFrame.height();
- final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
- final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
- AnimationSet set = new AnimationSet(true);
- final int surfaceInsetsH = surfaceInsets == null
- ? 0 : surfaceInsets.left + surfaceInsets.right;
- final int surfaceInsetsV = surfaceInsets == null
- ? 0 : surfaceInsets.top + surfaceInsets.bottom;
- // We want the scaling to happen from the center of the surface. In order to achieve that,
- // we need to account for surface insets that will be used to enlarge the surface.
- final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
- final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
- final ScaleAnimation scale = enter ?
- new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
- : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
- final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
- final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
- final int destHCenter = destFrame.left + destFrame.width() / 2;
- final int destVCenter = destFrame.top + destFrame.height() / 2;
- final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
- final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
- final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
- : new TranslateAnimation(0, fromX, 0, fromY);
- set.addAnimation(scale);
- set.addAnimation(translation);
- setAppTransitionFinishedCallbackIfNeeded(set);
- return set;
- }
-
- /**
- * @return true if and only if the first frame of the transition can be skipped, i.e. the first
- * frame of the transition doesn't change the visuals on screen, so we can start
- * directly with the second one
- */
- boolean canSkipFirstFrame() {
- return mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM
- && !mNextAppTransitionOverrideRequested
- && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE
- && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL
- && !mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY);
- }
-
- /**
- *
- * @param frame These are the bounds of the window when it finishes the animation. This is where
- * the animation must usually finish in entrance animation, as the next frame will
- * display the window at these coordinates. In case of exit animation, this is
- * where the animation must start, as the frame before the animation is displaying
- * the window at these bounds.
- * @param insets Knowing where the window will be positioned is not enough. Some parts of the
- * window might be obscured, usually by the system windows (status bar and
- * navigation bar) and we use content insets to convey that information. This
- * usually affects the animation aspects vertically, as the system decoration is
- * at the top and the bottom. For example when we animate from full screen to
- * recents, we want to exclude the covered parts, because they won't match the
- * thumbnail after the last frame is executed.
- * @param surfaceInsets In rare situation the surface is larger than the content and we need to
- * know about this to make the animation frames match. We currently use
- * this for freeform windows, which have larger surfaces to display
- * shadows. When we animate them from recents, we want to match the content
- * to the recents thumbnail and hence need to account for the surface being
- * bigger.
- */
- @Nullable
- Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode,
- int orientation, Rect frame, Rect displayFrame, Rect insets,
- @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
- boolean freeform, WindowContainer container) {
-
- final boolean canCustomizeAppTransition = container.canCustomizeAppTransition();
-
- if (mNextAppTransitionOverrideRequested) {
- if (canCustomizeAppTransition || mOverrideTaskTransition) {
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
- } else {
- ProtoLog.e(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: "
- + " override requested, but it is prohibited by policy.");
- }
- }
-
- Animation a;
- if (isKeyguardGoingAwayTransitOld(transit) && enter) {
- a = mTransitionAnimation.loadKeyguardExitAnimation(mNextAppTransitionFlags,
- transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
- } else if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE
- || transit == TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM) {
- a = null;
- } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE && !enter) {
- a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
- } else if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
- a = null;
- } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_OPEN
- || transit == TRANSIT_OLD_TASK_OPEN
- || transit == TRANSIT_OLD_TASK_TO_FRONT)) {
- a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a,
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_TASK_TO_BACK)) {
- a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a,
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (transit == TRANSIT_OLD_ACTIVITY_RELAUNCH) {
- a = mTransitionAnimation.createRelaunchAnimation(frame, insets,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s transit=%s Callers=%s", a,
- appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- a = getNextAppRequestedAnimation(enter);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
- + "isEntrance=%b Callers=%s",
- a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
- a = mTransitionAnimation.loadAppTransitionAnimation(
- mNextAppTransitionPackage, mNextAppTransitionInPlace);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM_IN_PLACE "
- + "transit=%s Callers=%s",
- a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = mTransitionAnimation.createClipRevealAnimationLockedCompat(
- transit, enter, frame, displayFrame,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL "
- + "transit=%s Callers=%s",
- a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
- a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s "
- + "isEntrance=%s Callers=%s",
- a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
- mNextAppTransitionScaleUp =
- (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
- final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
- a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter,
- mNextAppTransitionScaleUp, frame, transit, thumbnailHeader,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
- + "Callers=%s",
- a, mNextAppTransitionScaleUp
- ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN",
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
- mNextAppTransitionScaleUp =
- (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter,
- mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets,
- stableInsets, freeform, spec != null ? spec.rect : null,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
- + "Callers=%s",
- a, mNextAppTransitionScaleUp
- ? "ANIM_THUMBNAIL_ASPECT_SCALE_UP"
- : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN",
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS && enter) {
- a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: "
- + "anim=%s transit=%s isEntrance=true Callers=%s",
- a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (isChangeTransitOld(transit)) {
- // In the absence of a specific adapter, we just want to keep everything stationary.
- a = new AlphaAnimation(1.f, 1.f);
- a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s",
- a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else {
- int animAttr = mapOpenCloseTransitTypes(transit, enter);
- if (animAttr != 0) {
- final CustomAppTransition customAppTransition =
- getCustomAppTransition(animAttr, container);
- if (customAppTransition != null) {
- a = loadCustomActivityAnimation(customAppTransition, enter, container);
- } else {
- if (canCustomizeAppTransition) {
- a = loadAnimationAttr(lp, animAttr, transit);
- } else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, transit);
- }
- }
- } else {
- a = null;
- }
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
- + " canCustomizeAppTransition=%b Callers=%s",
- a, animAttr, appTransitionOldToString(transit), enter,
- canCustomizeAppTransition, Debug.getCallers(3));
- }
- setAppTransitionFinishedCallbackIfNeeded(a);
-
- return a;
- }
-
- CustomAppTransition getCustomAppTransition(int animAttr, WindowContainer container) {
- ActivityRecord customAnimationSource = container.asActivityRecord();
- if (customAnimationSource == null) {
- return null;
- }
-
- // Only top activity can customize activity animation.
- // If the animation is for the one below, try to get from the above activity.
- if (animAttr == WindowAnimation_activityOpenExitAnimation
- || animAttr == WindowAnimation_activityCloseEnterAnimation) {
- customAnimationSource = customAnimationSource.getTask()
- .getActivityAbove(customAnimationSource);
- if (customAnimationSource == null) {
- return null;
- }
- }
- switch (animAttr) {
- case WindowAnimation_activityOpenEnterAnimation:
- case WindowAnimation_activityOpenExitAnimation:
- return customAnimationSource.getCustomAnimation(true /* open */);
- case WindowAnimation_activityCloseEnterAnimation:
- case WindowAnimation_activityCloseExitAnimation:
- return customAnimationSource.getCustomAnimation(false /* open */);
- }
- return null;
- }
- private Animation loadCustomActivityAnimation(@NonNull CustomAppTransition custom,
- boolean enter, WindowContainer container) {
- final ActivityRecord customAnimationSource = container.asActivityRecord();
- final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
- customAnimationSource.packageName, enter
- ? custom.mEnterAnim : custom.mExitAnim);
- if (a != null && custom.mBackgroundColor != 0) {
- a.setBackdropColor(custom.mBackgroundColor);
- a.setShowBackdrop(true);
- }
- return a;
- }
-
- int getAppRootTaskClipMode() {
- return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH)
- || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY)
- || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL
- ? ROOT_TASK_CLIP_NONE
- : ROOT_TASK_CLIP_AFTER_ANIM;
- }
-
- @TransitionFlags
- public int getTransitFlags() {
- return mNextAppTransitionFlags;
- }
-
- void postAnimationCallback() {
- if (mNextAppTransitionCallback != null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(AppTransition::doAnimationCallback,
- mNextAppTransitionCallback));
- mNextAppTransitionCallback = null;
- }
- }
-
- void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
- @ColorInt int backgroundColor, IRemoteCallback startedCallback,
- IRemoteCallback endedCallback, boolean overrideTaskTransaction) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionOverrideRequested = true;
- mNextAppTransitionPackage = packageName;
- mNextAppTransitionEnter = enterAnim;
- mNextAppTransitionExit = exitAnim;
- mNextAppTransitionBackgroundColor = backgroundColor;
- postAnimationCallback();
- mNextAppTransitionCallback = startedCallback;
- mAnimationFinishedCallback = endedCallback;
- mOverrideTaskTransition = overrideTaskTransaction;
- }
- }
-
- void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
- int startHeight) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
- putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
- postAnimationCallback();
- }
- }
-
- void overridePendingAppTransitionClipReveal(int startX, int startY,
- int startWidth, int startHeight) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
- putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
- postAnimationCallback();
- }
- }
-
- void overridePendingAppTransitionThumb(HardwareBuffer srcThumb, int startX, int startY,
- IRemoteCallback startedCallback, boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
- mNextAppTransitionScaleUp = scaleUp;
- putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb);
- postAnimationCallback();
- mNextAppTransitionCallback = startedCallback;
- }
- }
-
- void overridePendingAppTransitionAspectScaledThumb(HardwareBuffer srcThumb, int startX,
- int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
- boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- mNextAppTransitionScaleUp = scaleUp;
- putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight,
- srcThumb);
- postAnimationCallback();
- mNextAppTransitionCallback = startedCallback;
- }
- }
-
- void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
- IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
- boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- mNextAppTransitionScaleUp = scaleUp;
- if (specs != null) {
- for (int i = 0; i < specs.length; i++) {
- AppTransitionAnimationSpec spec = specs[i];
- if (spec != null) {
- final PooledPredicate p = PooledLambda.obtainPredicate(
- Task::isTaskId, PooledLambda.__(Task.class), spec.taskId);
- final WindowContainer container = mDisplayContent.getTask(p);
- p.recycle();
- if (container == null) {
- continue;
- }
- mNextAppTransitionAnimationsSpecs.put(container.hashCode(), spec);
- if (i == 0) {
- // In full screen mode, the transition code depends on the default spec
- // to be set.
- Rect rect = spec.rect;
- putDefaultNextAppTransitionCoordinates(rect.left, rect.top,
- rect.width(), rect.height(), spec.buffer);
- }
- }
- }
- }
- postAnimationCallback();
- mNextAppTransitionCallback = onAnimationStartedCallback;
- mAnimationFinishedCallback = onAnimationFinishedCallback;
- }
- }
-
- void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
- boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- mNextAppTransitionAnimationsSpecsFuture = specsFuture;
- mNextAppTransitionScaleUp = scaleUp;
- mNextAppTransitionFutureCallback = callback;
- if (isReady()) {
- fetchAppTransitionSpecsFromFuture();
- }
- }
- }
-
- void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
- overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */,
- false /* isActivityEmbedding*/);
- }
-
- void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
- boolean sync, boolean isActivityEmbedding) {
- }
-
- void overrideInPlaceAppTransition(String packageName, int anim) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
- mNextAppTransitionPackage = packageName;
- mNextAppTransitionInPlace = anim;
- }
- }
-
- /**
- * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS}
- */
- void overridePendingAppTransitionStartCrossProfileApps() {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
- postAnimationCallback();
- }
- }
-
- private boolean canOverridePendingAppTransition() {
- // Remote animations always take precedence
- return isTransitionSet() && mNextAppTransitionType != NEXT_TRANSIT_TYPE_REMOTE;
- }
-
- /**
- * If a future is set for the app transition specs, fetch it in another thread.
- */
- private void fetchAppTransitionSpecsFromFuture() {
- if (mNextAppTransitionAnimationsSpecsFuture != null) {
- mNextAppTransitionAnimationsSpecsPending = true;
- final IAppTransitionAnimationSpecsFuture future
- = mNextAppTransitionAnimationsSpecsFuture;
- mNextAppTransitionAnimationsSpecsFuture = null;
- mDefaultExecutor.execute(() -> {
- AppTransitionAnimationSpec[] specs = null;
- try {
- Binder.allowBlocking(future.asBinder());
- specs = future.get();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to fetch app transition specs: " + e);
- }
- synchronized (mService.mGlobalLock) {
- mNextAppTransitionAnimationsSpecsPending = false;
- overridePendingAppTransitionMultiThumb(specs,
- mNextAppTransitionFutureCallback, null /* finishedCallback */,
- mNextAppTransitionScaleUp);
- mNextAppTransitionFutureCallback = null;
- mService.requestTraversal();
- }
- });
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("mNextAppTransitionRequests=[");
-
- boolean separator = false;
- for (Integer transit : mNextAppTransitionRequests) {
- if (separator) {
- sb.append(", ");
- }
- sb.append(appTransitionToString(transit));
- separator = true;
- }
- sb.append("]");
- sb.append(", mNextAppTransitionFlags="
- + appTransitionFlagsToString(mNextAppTransitionFlags));
- return sb.toString();
- }
-
- /**
- * Returns the human readable name of a old window transition.
- *
- * @param transition The old window transition.
- * @return The transition symbolic name.
- */
- public static String appTransitionOldToString(@TransitionOldType int transition) {
- switch (transition) {
- case TRANSIT_OLD_UNSET: {
- return "TRANSIT_OLD_UNSET";
- }
- case TRANSIT_OLD_NONE: {
- return "TRANSIT_OLD_NONE";
- }
- case TRANSIT_OLD_ACTIVITY_OPEN: {
- return "TRANSIT_OLD_ACTIVITY_OPEN";
- }
- case TRANSIT_OLD_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_ACTIVITY_CLOSE";
- }
- case TRANSIT_OLD_TASK_OPEN: {
- return "TRANSIT_OLD_TASK_OPEN";
- }
- case TRANSIT_OLD_TASK_CLOSE: {
- return "TRANSIT_OLD_TASK_CLOSE";
- }
- case TRANSIT_OLD_TASK_TO_FRONT: {
- return "TRANSIT_OLD_TASK_TO_FRONT";
- }
- case TRANSIT_OLD_TASK_TO_BACK: {
- return "TRANSIT_OLD_TASK_TO_BACK";
- }
- case TRANSIT_OLD_WALLPAPER_CLOSE: {
- return "TRANSIT_OLD_WALLPAPER_CLOSE";
- }
- case TRANSIT_OLD_WALLPAPER_OPEN: {
- return "TRANSIT_OLD_WALLPAPER_OPEN";
- }
- case TRANSIT_OLD_WALLPAPER_INTRA_OPEN: {
- return "TRANSIT_OLD_WALLPAPER_INTRA_OPEN";
- }
- case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE: {
- return "TRANSIT_OLD_WALLPAPER_INTRA_CLOSE";
- }
- case TRANSIT_OLD_TASK_OPEN_BEHIND: {
- return "TRANSIT_OLD_TASK_OPEN_BEHIND";
- }
- case TRANSIT_OLD_ACTIVITY_RELAUNCH: {
- return "TRANSIT_OLD_ACTIVITY_RELAUNCH";
- }
- case TRANSIT_OLD_KEYGUARD_GOING_AWAY: {
- return "TRANSIT_OLD_KEYGUARD_GOING_AWAY";
- }
- case TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
- return "TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
- }
- case TRANSIT_OLD_KEYGUARD_OCCLUDE: {
- return "TRANSIT_OLD_KEYGUARD_OCCLUDE";
- }
- case TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM: {
- return "TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM";
- }
- case TRANSIT_OLD_KEYGUARD_UNOCCLUDE: {
- return "TRANSIT_OLD_KEYGUARD_UNOCCLUDE";
- }
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN: {
- return "TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN";
- }
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE";
- }
- case TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE";
- }
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN: {
- return "TRANSIT_OLD_TASK_FRAGMENT_OPEN";
- }
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: {
- return "TRANSIT_OLD_TASK_FRAGMENT_CLOSE";
- }
- case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: {
- return "TRANSIT_OLD_TASK_FRAGMENT_CHANGE";
- }
- case TRANSIT_OLD_DREAM_ACTIVITY_OPEN: {
- return "TRANSIT_OLD_DREAM_ACTIVITY_OPEN";
- }
- case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_DREAM_ACTIVITY_CLOSE";
- }
- default: {
- return "<UNKNOWN: " + transition + ">";
- }
- }
- }
-
- /**
- * Returns the human readable name of a window transition.
- *
- * @param transition The window transition.
- * @return The transition symbolic name.
- */
- public static String appTransitionToString(@TransitionType int transition) {
- switch (transition) {
- case TRANSIT_NONE: {
- return "TRANSIT_NONE";
- }
- case TRANSIT_OPEN: {
- return "TRANSIT_OPEN";
- }
- case TRANSIT_CLOSE: {
- return "TRANSIT_CLOSE";
- }
- case TRANSIT_TO_FRONT: {
- return "TRANSIT_TO_FRONT";
- }
- case TRANSIT_TO_BACK: {
- return "TRANSIT_TO_BACK";
- }
- case TRANSIT_RELAUNCH: {
- return "TRANSIT_RELAUNCH";
- }
- case TRANSIT_CHANGE: {
- return "TRANSIT_CHANGE";
- }
- case TRANSIT_KEYGUARD_GOING_AWAY: {
- return "TRANSIT_KEYGUARD_GOING_AWAY";
- }
- case TRANSIT_KEYGUARD_OCCLUDE: {
- return "TRANSIT_KEYGUARD_OCCLUDE";
- }
- case TRANSIT_KEYGUARD_UNOCCLUDE: {
- return "TRANSIT_KEYGUARD_UNOCCLUDE";
- }
- default: {
- return "<UNKNOWN: " + transition + ">";
- }
- }
- }
-
- private String appStateToString() {
- switch (mAppTransitionState) {
- case APP_STATE_IDLE:
- return "APP_STATE_IDLE";
- case APP_STATE_READY:
- return "APP_STATE_READY";
- case APP_STATE_RUNNING:
- return "APP_STATE_RUNNING";
- case APP_STATE_TIMEOUT:
- return "APP_STATE_TIMEOUT";
- default:
- return "unknown state=" + mAppTransitionState;
- }
- }
-
- private String transitTypeToString() {
- switch (mNextAppTransitionType) {
- case NEXT_TRANSIT_TYPE_NONE:
- return "NEXT_TRANSIT_TYPE_NONE";
- case NEXT_TRANSIT_TYPE_CUSTOM:
- return "NEXT_TRANSIT_TYPE_CUSTOM";
- case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
- return "NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE";
- case NEXT_TRANSIT_TYPE_SCALE_UP:
- return "NEXT_TRANSIT_TYPE_SCALE_UP";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN";
- case NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:
- return "NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS";
- default:
- return "unknown type=" + mNextAppTransitionType;
- }
- }
-
- private static final ArrayList<Pair<Integer, String>> sFlagToString;
-
- static {
- sFlagToString = new ArrayList<>();
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION"));
- sFlagToString.add(new Pair<>(
- TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_WITH_IN_WINDOW_ANIMATIONS"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_APP_CRASHED,
- "TRANSIT_FLAG_APP_CRASHED"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_OPEN_BEHIND,
- "TRANSIT_FLAG_OPEN_BEHIND"));
- }
-
- /**
- * Returns the human readable names of transit flags.
- *
- * @param flags a bitmask combination of transit flags.
- * @return The combination of symbolic names.
- */
- public static String appTransitionFlagsToString(int flags) {
- String sep = "";
- StringBuilder sb = new StringBuilder();
- for (Pair<Integer, String> pair : sFlagToString) {
- if ((flags & pair.first) != 0) {
- sb.append(sep);
- sb.append(pair.second);
- sep = " | ";
- }
- }
- return sb.toString();
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(APP_TRANSITION_STATE, mAppTransitionState);
- proto.write(LAST_USED_APP_TRANSITION, mLastUsedAppTransition);
- proto.end(token);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.println(this);
- pw.print(prefix); pw.print("mAppTransitionState="); pw.println(appStateToString());
- if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
- pw.print(prefix); pw.print("mNextAppTransitionType=");
- pw.println(transitTypeToString());
- }
- if (mNextAppTransitionOverrideRequested
- || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- pw.print(prefix); pw.print("mNextAppTransitionPackage=");
- pw.println(mNextAppTransitionPackage);
- pw.print(prefix); pw.print("mNextAppTransitionEnter=0x");
- pw.print(Integer.toHexString(mNextAppTransitionEnter));
- pw.print(" mNextAppTransitionExit=0x");
- pw.println(Integer.toHexString(mNextAppTransitionExit));
- pw.print(" mNextAppTransitionBackgroundColor=0x");
- pw.println(Integer.toHexString(mNextAppTransitionBackgroundColor));
- }
- switch (mNextAppTransitionType) {
- case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
- pw.print(prefix); pw.print("mNextAppTransitionPackage=");
- pw.println(mNextAppTransitionPackage);
- pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x");
- pw.print(Integer.toHexString(mNextAppTransitionInPlace));
- break;
- case NEXT_TRANSIT_TYPE_SCALE_UP: {
- getDefaultNextAppTransitionStartRect(mTmpRect);
- pw.print(prefix); pw.print("mNextAppTransitionStartX=");
- pw.print(mTmpRect.left);
- pw.print(" mNextAppTransitionStartY=");
- pw.println(mTmpRect.top);
- pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
- pw.print(mTmpRect.width());
- pw.print(" mNextAppTransitionStartHeight=");
- pw.println(mTmpRect.height());
- break;
- }
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: {
- pw.print(prefix); pw.print("mDefaultNextAppTransitionAnimationSpec=");
- pw.println(mDefaultNextAppTransitionAnimationSpec);
- pw.print(prefix); pw.print("mNextAppTransitionAnimationsSpecs=");
- pw.println(mNextAppTransitionAnimationsSpecs);
- pw.print(prefix); pw.print("mNextAppTransitionScaleUp=");
- pw.println(mNextAppTransitionScaleUp);
- break;
- }
- }
- if (mNextAppTransitionCallback != null) {
- pw.print(prefix); pw.print("mNextAppTransitionCallback=");
- pw.println(mNextAppTransitionCallback);
- }
- if (mLastUsedAppTransition != TRANSIT_OLD_NONE) {
- pw.print(prefix); pw.print("mLastUsedAppTransition=");
- pw.println(appTransitionOldToString(mLastUsedAppTransition));
- pw.print(prefix); pw.print("mLastOpeningApp=");
- pw.println(mLastOpeningApp);
- pw.print(prefix); pw.print("mLastClosingApp=");
- pw.println(mLastClosingApp);
- pw.print(prefix); pw.print("mLastChangingApp=");
- pw.println(mLastChangingApp);
- }
- }
-
- boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
- if (WindowManagerService.sEnableShellTransitions) {
- return false;
- }
- mNextAppTransitionRequests.add(transit);
- mNextAppTransitionFlags |= flags;
- updateBooster();
- removeAppTransitionTimeoutCallbacks();
- mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable,
- APP_TRANSITION_TIMEOUT_MS);
- return prepare();
- }
-
- /**
- * @return true if {@param transit} is representing a transition in which Keyguard is going
- * away, false otherwise
- */
- public static boolean isKeyguardGoingAwayTransitOld(int transit) {
- return transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
- }
-
- static boolean isKeyguardOccludeTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_KEYGUARD_OCCLUDE
- || transit == TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM
- || transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
- }
-
- static boolean isKeyguardTransitOld(@TransitionOldType int transit) {
- return isKeyguardGoingAwayTransitOld(transit) || isKeyguardOccludeTransitOld(transit);
- }
-
- static boolean isTaskTransitOld(@TransitionOldType int transit) {
- return isTaskOpenTransitOld(transit)
- || isTaskCloseTransitOld(transit);
- }
-
- static boolean isTaskCloseTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_TASK_TO_BACK;
- }
-
- private static boolean isTaskOpenTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_OPEN
- || transit == TRANSIT_OLD_TASK_OPEN_BEHIND
- || transit == TRANSIT_OLD_TASK_TO_FRONT;
- }
-
- static boolean isActivityTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_ACTIVITY_OPEN
- || transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_ACTIVITY_RELAUNCH;
- }
-
- static boolean isTaskFragmentTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_FRAGMENT_OPEN
- || transit == TRANSIT_OLD_TASK_FRAGMENT_CLOSE
- || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
- }
-
- static boolean isChangeTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
- || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
- }
-
- static boolean isClosingTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_WALLPAPER_CLOSE
- || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE
- || transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
- }
-
- static boolean isNormalTransit(@TransitionType int transit) {
- return transit == TRANSIT_OPEN
- || transit == TRANSIT_CLOSE
- || transit == TRANSIT_TO_FRONT
- || transit == TRANSIT_TO_BACK;
- }
-
- static boolean isKeyguardTransit(@TransitionType int transit) {
- return transit == TRANSIT_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_KEYGUARD_OCCLUDE
- || transit == TRANSIT_KEYGUARD_UNOCCLUDE;
- }
-
- @TransitionType int getKeyguardTransition() {
- if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) {
- return TRANSIT_KEYGUARD_GOING_AWAY;
- }
- final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
- final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE);
- // No keyguard related transition requests.
- if (unoccludeIndex == -1 && occludeIndex == -1) {
- return TRANSIT_NONE;
- }
- // In case we unocclude Keyguard and occlude it again, meaning that we never actually
- // unoccclude/occlude Keyguard, but just run a normal transition.
- if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) {
- return TRANSIT_NONE;
- }
- return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE;
- }
-
- @TransitionType int getFirstAppTransition() {
- for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) {
- final @TransitionType int transit = mNextAppTransitionRequests.get(i);
- if (transit != TRANSIT_NONE && !isKeyguardTransit(transit)) {
- return transit;
- }
- }
- return TRANSIT_NONE;
- }
-
- boolean containsTransitRequest(@TransitionType int transit) {
- return mNextAppTransitionRequests.contains(transit);
- }
-
- private void handleAppTransitionTimeout() {
- }
-
- private static void doAnimationCallback(@NonNull IRemoteCallback callback) {
- try {
- ((IRemoteCallback) callback).sendResult(null);
- } catch (RemoteException e) {
- }
- }
-
- private void setAppTransitionFinishedCallbackIfNeeded(Animation anim) {
- final IRemoteCallback callback = mAnimationFinishedCallback;
- if (callback != null && anim != null) {
- anim.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) { }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppTransition::doAnimationCallback, callback));
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) { }
- });
- }
- }
-
- void removeAppTransitionTimeoutCallbacks() {
- mHandler.removeCallbacks(mHandleAppTransitionTimeoutRunnable);
- }
-}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e9d9ace825c0..dfe323c43abb 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -479,21 +479,16 @@ class BackNavigationController {
}
} else {
// If adjacent TF has companion to current TF, those two TF will be closed together.
- final TaskFragment adjacentTF;
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- if (currTF.getAdjacentTaskFragments().size() > 2) {
- throw new IllegalStateException(
- "Not yet support 3+ adjacent for non-Task TFs");
- }
- final TaskFragment[] tmpAdjacent = new TaskFragment[1];
- currTF.forOtherAdjacentTaskFragments(tf -> {
- tmpAdjacent[0] = tf;
- return true;
- });
- adjacentTF = tmpAdjacent[0];
- } else {
- adjacentTF = currTF.getAdjacentTaskFragment();
+ if (currTF.getAdjacentTaskFragments().size() > 2) {
+ throw new IllegalStateException(
+ "Not yet support 3+ adjacent for non-Task TFs");
}
+ final TaskFragment[] tmpAdjacent = new TaskFragment[1];
+ currTF.forOtherAdjacentTaskFragments(tf -> {
+ tmpAdjacent[0] = tf;
+ return true;
+ });
+ final TaskFragment adjacentTF = tmpAdjacent[0];
if (isSecondCompanionToFirst(currTF, adjacentTF)) {
// The two TFs are adjacent (visually displayed side-by-side), search if any
// activity below the lowest one.
@@ -553,15 +548,6 @@ class BackNavigationController {
if (!prevTF.hasAdjacentTaskFragment()) {
return;
}
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
- final ActivityRecord prevActivityAdjacent =
- prevTFAdjacent.getTopNonFinishingActivity();
- if (prevActivityAdjacent != null) {
- outPrevActivities.add(prevActivityAdjacent);
- }
- return;
- }
prevTF.forOtherAdjacentTaskFragments(prevTFAdjacent -> {
final ActivityRecord prevActivityAdjacent =
prevTFAdjacent.getTopNonFinishingActivity();
@@ -577,14 +563,6 @@ class BackNavigationController {
if (mainTF == null || !mainTF.hasAdjacentTaskFragment()) {
return;
}
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
- final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
- if (topActivity != null) {
- outList.add(topActivity);
- }
- return;
- }
mainTF.forOtherAdjacentTaskFragments(adjacentTF -> {
final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
if (topActivity != null) {
@@ -691,8 +669,8 @@ class BackNavigationController {
return false;
}
if (window.mAttrs.windowAnimations != 0) {
- final TransitionAnimation transitionAnimation = window.getDisplayContent()
- .mAppTransition.mTransitionAnimation;
+ final TransitionAnimation transitionAnimation = window.mDisplayContent
+ .mTransitionAnimation;
final int attr = com.android.internal.R.styleable
.WindowAnimation_activityCloseExitAnimation;
final int appResId = transitionAnimation.getAnimationResId(
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 36686fc086f1..f50a68cc5389 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -49,8 +49,8 @@ import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskS
import static com.android.window.flags.Flags.balImprovedMetrics;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.window.flags.Flags.balStrictModeRo;
import static com.android.window.flags.Flags.balStrictModeGracePeriod;
+import static com.android.window.flags.Flags.balStrictModeRo;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -91,7 +91,6 @@ import com.android.internal.util.Preconditions;
import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration;
-import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -288,9 +287,10 @@ public class BackgroundActivityStartController {
}
private boolean isHomeApp(int uid, @Nullable String packageName) {
- if (getService().mHomeProcess != null) {
- // Fast check
- return uid == getService().mHomeProcess.mUid;
+ WindowProcessController homeProcess = getService().mHomeProcess;
+ if (homeProcess != null && (homeProcess.mUid != SYSTEM_UID || packageName == null)) {
+ // Fast check (skip if sharing system UID and we have a package name)
+ return uid == homeProcess.mUid;
}
if (packageName == null) {
return false;
@@ -1686,14 +1686,6 @@ public class BackgroundActivityStartController {
}
// Check the adjacent fragment.
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
- if (topActivity == null) {
- return bas;
- }
- return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
- }
final BlockActivityStart[] out = { bas };
taskFragment.forOtherAdjacentTaskFragments(adjacentTaskFragment -> {
final ActivityRecord top = adjacentTaskFragment.getActivity(topOfStackPredicate);
diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
index 5db02dff8351..aaa5a0074506 100644
--- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
@@ -16,10 +16,20 @@
package com.android.server.wm;
+import static com.android.server.wm.AbsAppSnapshotController.TAG;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.window.TaskSnapshot;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
+
import java.io.File;
+import java.util.UUID;
class BaseAppSnapshotPersister {
static final String LOW_RES_FILE_POSTFIX = "_reduced";
@@ -29,6 +39,7 @@ class BaseAppSnapshotPersister {
// Shared with SnapshotPersistQueue
protected final Object mLock;
protected final SnapshotPersistQueue mSnapshotPersistQueue;
+ @VisibleForTesting
protected final PersistInfoProvider mPersistInfoProvider;
BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue,
@@ -79,6 +90,8 @@ class BaseAppSnapshotPersister {
private final boolean mEnableLowResSnapshots;
private final float mLowResScaleFactor;
private final boolean mUse16BitFormat;
+ private final SparseBooleanArray mInitializedUsers = new SparseBooleanArray();
+ private final SparseArray<File> mScrambleDirectories = new SparseArray<>();
PersistInfoProvider(DirectoryResolver directoryResolver, String dirName,
boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat) {
@@ -91,9 +104,80 @@ class BaseAppSnapshotPersister {
@NonNull
File getDirectory(int userId) {
+ if (Flags.scrambleSnapshotFileName()) {
+ final File directory = getOrInitScrambleDirectory(userId);
+ if (directory != null) {
+ return directory;
+ }
+ }
+ return getBaseDirectory(userId);
+ }
+
+ @NonNull
+ private File getBaseDirectory(int userId) {
return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName);
}
+ @Nullable
+ private File getOrInitScrambleDirectory(int userId) {
+ synchronized (mScrambleDirectories) {
+ if (mInitializedUsers.get(userId)) {
+ return mScrambleDirectories.get(userId);
+ }
+ mInitializedUsers.put(userId, true);
+ final File scrambledDirectory = getScrambleDirectory(userId);
+ final File baseDir = getBaseDirectory(userId);
+ String newName = null;
+ // If directory exists, rename
+ if (scrambledDirectory.exists()) {
+ newName = UUID.randomUUID().toString();
+ final File scrambleTo = new File(baseDir, newName);
+ if (!scrambledDirectory.renameTo(scrambleTo)) {
+ Slog.w(TAG, "SnapshotPersister rename scramble folder fail.");
+ return null;
+ }
+ } else {
+ // If directory not exists, mkDir.
+ if (!baseDir.exists() && !baseDir.mkdir()) {
+ Slog.w(TAG, "SnapshotPersister make base folder fail.");
+ return null;
+ }
+ if (!scrambledDirectory.mkdir()) {
+ Slog.e(TAG, "SnapshotPersister make scramble folder fail");
+ return null;
+ }
+ // Move any existing files to this folder.
+ final String[] files = baseDir.list();
+ if (files != null) {
+ for (String file : files) {
+ final File original = new File(baseDir, file);
+ if (original.isDirectory()) {
+ newName = file;
+ } else {
+ File to = new File(scrambledDirectory, file);
+ original.renameTo(to);
+ }
+ }
+ }
+ }
+ final File newFolder = new File(baseDir, newName);
+ mScrambleDirectories.put(userId, newFolder);
+ return newFolder;
+ }
+ }
+
+ @NonNull
+ private File getScrambleDirectory(int userId) {
+ final File dir = getBaseDirectory(userId);
+ final String[] directories = dir.list(
+ (current, name) -> new File(current, name).isDirectory());
+ if (directories != null && directories.length > 0) {
+ return new File(dir, directories[0]);
+ } else {
+ return new File(dir, UUID.randomUUID().toString());
+ }
+ }
+
/**
* Return if task snapshots are stored in 16 bit pixel format.
*
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index c26acec19743..1c2569251581 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -38,6 +38,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
+import android.window.DesktopExperienceFlags;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
@@ -353,6 +354,13 @@ final class ContentRecorder implements WindowContainerListener {
return;
}
+ // Recording should not be started on displays that are eligible for hosting tasks.
+ // See android.view.Display#canHostTasks().
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()
+ && mDisplayContent.mDisplay.canHostTasks()) {
+ return;
+ }
+
if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
+ "nothing");
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index f473b7b7e4fb..b8027546fdbd 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.view.WindowManager.TRANSIT_CHANGE;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
@@ -38,7 +38,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.protolog.ProtoLog;
import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
-import com.android.window.flags.Flags;
import java.util.Arrays;
import java.util.Objects;
@@ -140,8 +139,9 @@ class DeferredDisplayUpdater {
if (displayInfoDiff == DIFF_EVERYTHING
|| !mDisplayContent.getLastHasContent()
|| !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applying DisplayInfo immediately");
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) immediately",
+ displayInfo.logicalWidth, displayInfo.logicalHeight);
mLastWmDisplayInfo = displayInfo;
applyLatestDisplayInfo();
@@ -151,17 +151,23 @@ class DeferredDisplayUpdater {
// If there are non WM-specific display info changes, apply only these fields immediately
if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) {
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: partially applying DisplayInfo immediately");
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "DeferredDisplayUpdater: partially applying DisplayInfo(%d x %d) immediately",
+ displayInfo.logicalWidth, displayInfo.logicalHeight);
applyLatestDisplayInfo();
}
// If there are WM-specific display info changes, apply them through a Shell transition
if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) {
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: deferring DisplayInfo update");
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "DeferredDisplayUpdater: deferring DisplayInfo(%d x %d) update",
+ displayInfo.logicalWidth, displayInfo.logicalHeight);
requestDisplayChangeTransition(physicalDisplayUpdated, () -> {
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) after deferring",
+ displayInfo.logicalWidth, displayInfo.logicalHeight);
+
// Apply deferrable fields to DisplayContent only when the transition
// starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo
mLastWmDisplayInfo = displayInfo;
@@ -199,7 +205,7 @@ class DeferredDisplayUpdater {
mDisplayContent.getDisplayPolicy().getNotificationShade();
if (notificationShade != null && notificationShade.isVisible()
&& mDisplayContent.mAtmService.mKeyguardController.isKeyguardOrAodShowing(
- mDisplayContent.mDisplayId)) {
+ mDisplayContent.mDisplayId)) {
Slog.i(TAG, notificationShade + " uses blast for display switch");
notificationShade.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
}
@@ -209,9 +215,6 @@ class DeferredDisplayUpdater {
try {
onStartCollect.run();
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applied DisplayInfo after deferring");
-
if (physicalDisplayUpdated) {
onDisplayUpdated(transition, fromRotation, startBounds);
} else {
@@ -332,7 +335,6 @@ class DeferredDisplayUpdater {
/** Returns {@code true} if the transition will control when to turn on the screen. */
boolean waitForTransition(@NonNull Message screenUnblocker) {
- if (!Flags.waitForTransitionOnDisplaySwitch()) return false;
if (!mShouldWaitForTransitionWhenScreenOn) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 7a959c14fbd2..d9354323ae7c 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -24,6 +24,8 @@ import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx;
+import static com.android.internal.policy.DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds;
import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
@@ -64,49 +66,61 @@ public final class DesktopModeBoundsCalculator {
*/
static void updateInitialBounds(@NonNull Task task, @Nullable WindowLayout layout,
@Nullable ActivityRecord activity, @Nullable ActivityOptions options,
- @NonNull Rect outBounds, @NonNull Consumer<String> logger) {
+ @NonNull LaunchParamsController.LaunchParams outParams,
+ @NonNull Consumer<String> logger) {
// Use stable frame instead of raw frame to avoid launching freeform windows on top of
// stable insets, which usually are system widgets such as sysbar & navbar.
final Rect stableBounds = new Rect();
task.getDisplayArea().getStableRect(stableBounds);
- // If the options bounds size is flexible, update size with calculated desired size.
+ final boolean hasFullscreenOverride = activity != null
+ && activity.mAppCompatController.getAspectRatioOverrides().hasFullscreenOverride();
+ // If the options bounds size is flexible and no fullscreen override has been applied,
+ // update size with calculated desired size.
final boolean updateOptionBoundsSize = options != null
- && options.getFlexibleLaunchSize();
+ && options.getFlexibleLaunchSize() && !hasFullscreenOverride;
// If cascading is also enabled, the position of the options bounds must be respected
// during the size update.
final boolean shouldRespectOptionPosition =
updateOptionBoundsSize && DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue();
+ final int captionHeight = activity != null && shouldExcludeCaptionFromAppBounds(
+ activity.info, task.isResizeable(), activity.mOptOutEdgeToEdge)
+ ? getDesktopViewAppHeaderHeightPx(activity.mWmService.mContext) : 0;
if (options != null && options.getLaunchBounds() != null
&& !updateOptionBoundsSize) {
- outBounds.set(options.getLaunchBounds());
- logger.accept("inherit-from-options=" + outBounds);
+ outParams.mBounds.set(options.getLaunchBounds());
+ logger.accept("inherit-from-options=" + outParams.mBounds);
} else if (layout != null) {
final int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
final int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if (layout.hasSpecifiedSize()) {
- calculateLayoutBounds(stableBounds, layout, outBounds,
+ calculateLayoutBounds(stableBounds, layout, outParams.mBounds,
calculateIdealSize(stableBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
- applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
+ applyLayoutGravity(verticalGravity, horizontalGravity, outParams.mBounds,
stableBounds);
logger.accept("layout specifies sizes, inheriting size and applying gravity");
} else if (verticalGravity > 0 || horizontalGravity > 0) {
- outBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
- shouldRespectOptionPosition));
- applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
+ outParams.mBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
+ shouldRespectOptionPosition, captionHeight));
+ applyLayoutGravity(verticalGravity, horizontalGravity, outParams.mBounds,
stableBounds);
logger.accept("layout specifies gravity, applying desired bounds and gravity");
logger.accept("respecting option bounds cascaded position="
+ shouldRespectOptionPosition);
}
} else {
- outBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
- shouldRespectOptionPosition));
+ outParams.mBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
+ shouldRespectOptionPosition, captionHeight));
logger.accept("layout not specified, applying desired bounds");
logger.accept("respecting option bounds cascaded position="
+ shouldRespectOptionPosition);
}
+ if (updateOptionBoundsSize && captionHeight != 0) {
+ outParams.mAppBounds.set(outParams.mBounds);
+ outParams.mAppBounds.top += captionHeight;
+ logger.accept("excluding caption height from app bounds");
+ }
}
/**
@@ -119,7 +133,8 @@ public final class DesktopModeBoundsCalculator {
@NonNull
private static Rect calculateInitialBounds(@NonNull Task task,
@NonNull ActivityRecord activity, @NonNull Rect stableBounds,
- @Nullable ActivityOptions options, boolean shouldRespectOptionPosition
+ @Nullable ActivityOptions options, boolean shouldRespectOptionPosition,
+ int captionHeight
) {
// Display bounds not taking into account insets.
final TaskDisplayArea displayArea = task.getDisplayArea();
@@ -160,7 +175,8 @@ public final class DesktopModeBoundsCalculator {
}
// If activity is unresizeable, regardless of orientation, calculate maximum size
// (within the ideal size) maintaining original aspect ratio.
- yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio);
+ yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio,
+ captionHeight);
}
case ORIENTATION_PORTRAIT -> {
// Device in portrait orientation.
@@ -188,11 +204,12 @@ public final class DesktopModeBoundsCalculator {
// ratio.
yield maximizeSizeGivenAspectRatio(activityOrientation,
new Size(customPortraitWidthForLandscapeApp, idealSize.getHeight()),
- appAspectRatio);
+ appAspectRatio, captionHeight);
}
// For portrait unresizeable activities, calculate maximum size (within the ideal
// size) maintaining original aspect ratio.
- yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio);
+ yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio,
+ captionHeight);
}
default -> idealSize;
};
@@ -232,13 +249,15 @@ public final class DesktopModeBoundsCalculator {
* Calculates the largest size that can fit in a given area while maintaining a specific aspect
* ratio.
*/
+ // TODO(b/400617906): Merge duplicate initial bounds calculations to shared class.
@NonNull
private static Size maximizeSizeGivenAspectRatio(
@ScreenOrientation int orientation,
@NonNull Size targetArea,
- float aspectRatio
+ float aspectRatio,
+ int captionHeight
) {
- final int targetHeight = targetArea.getHeight();
+ final int targetHeight = targetArea.getHeight() - captionHeight;
final int targetWidth = targetArea.getWidth();
final int finalHeight;
final int finalWidth;
@@ -275,7 +294,7 @@ public final class DesktopModeBoundsCalculator {
finalHeight = (int) (finalWidth / aspectRatio);
}
}
- return new Size(finalWidth, finalHeight);
+ return new Size(finalWidth, finalHeight + captionHeight);
}
/**
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index dc42b32967e2..d91fca9e2816 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.Flags.enableConnectedDisplaysWallpaper;
+import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE;
import android.annotation.NonNull;
import android.content.Context;
@@ -66,7 +67,7 @@ public final class DesktopModeHelper {
* Return {@code true} if the current device can hosts desktop sessions on its internal display.
*/
@VisibleForTesting
- static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
+ private static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
}
@@ -83,8 +84,11 @@ public final class DesktopModeHelper {
if (!shouldEnforceDeviceRestrictions()) {
return true;
}
- final boolean desktopModeSupported = isDesktopModeSupported(context)
- && canInternalDisplayHostDesktops(context);
+ // If projected display is enabled, #canInternalDisplayHostDesktops is no longer a
+ // requirement.
+ final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue()
+ ? isDesktopModeSupported(context) : (isDesktopModeSupported(context)
+ && canInternalDisplayHostDesktops(context));
final boolean desktopModeSupportedByDevOptions =
Flags.enableDesktopModeThroughDevOption()
&& isDesktopModeDevOptionsSupported(context);
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index d466a646c7dd..03ba1a51ad7b 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -19,6 +19,12 @@ package com.android.server.wm;
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.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -107,14 +113,23 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
// Copy over any values
outParams.set(currentParams);
- // In Proto2, trampoline task launches of an existing background task can result in the
- // previous windowing mode to be restored even if the desktop mode state has changed.
- // Let task launches inherit the windowing mode from the source task if available, which
- // should have the desired windowing mode set by WM Shell. See b/286929122.
if (source != null && source.getTask() != null) {
final Task sourceTask = source.getTask();
- outParams.mWindowingMode = sourceTask.getWindowingMode();
- appendLog("inherit-from-source=" + outParams.mWindowingMode);
+ if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue()
+ && isEnteringDesktopMode(sourceTask, options, currentParams)) {
+ // If trampoline source is not freeform but we are entering or in desktop mode,
+ // ignore the source windowing mode and set the windowing mode to freeform
+ outParams.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ appendLog("freeform window mode applied to task trampoline");
+ } else {
+ // In Proto2, trampoline task launches of an existing background task can result in
+ // the previous windowing mode to be restored even if the desktop mode state has
+ // changed. Let task launches inherit the windowing mode from the source task if
+ // available, which should have the desired windowing mode set by WM Shell.
+ // See b/286929122.
+ outParams.mWindowingMode = sourceTask.getWindowingMode();
+ appendLog("inherit-from-source=" + outParams.mWindowingMode);
+ }
}
if (phase == PHASE_WINDOWING_MODE) {
@@ -127,12 +142,29 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
}
if ((options == null || options.getLaunchBounds() == null) && task.hasOverrideBounds()) {
+ if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue()) {
+ // We are in desktop, return result done to prevent other modifiers from modifying
+ // exiting task bounds or resolved windowing mode.
+ return RESULT_DONE;
+ }
appendLog("current task has bounds set, not overriding");
return RESULT_SKIP;
}
+ if (DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue()) {
+ ActivityRecord topVisibleFreeformActivity =
+ task.getDisplayContent().getTopMostVisibleFreeformActivity();
+ if (shouldInheritExistingTaskBounds(topVisibleFreeformActivity, activity, task)) {
+ appendLog("inheriting bounds from existing closing instance");
+ outParams.mBounds.set(topVisibleFreeformActivity.getBounds());
+ appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
+ // Return result done to prevent other modifiers from changing or cascading bounds.
+ return RESULT_DONE;
+ }
+ }
+
DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options,
- outParams.mBounds, this::appendLog);
+ outParams, this::appendLog);
appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
if (options != null && options.getFlexibleLaunchSize()) {
// Return result done to prevent other modifiers from respecting option bounds and
@@ -159,7 +191,7 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
// activity will also enter desktop mode. On this same relationship, we can also assume
// if there are not visible freeform tasks but a freeform activity is now launching, it
// will force the device into desktop mode.
- return (task.getDisplayContent().getTopMostVisibleFreeformActivity() != null
+ return (task.getDisplayContent().getTopMostFreeformActivity() != null
&& checkSourceWindowModesCompatible(task, options, currentParams))
|| isRequestingFreeformWindowMode(task, options, currentParams);
}
@@ -201,6 +233,40 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
};
}
+ /**
+ * Whether the launching task should inherit the task bounds of an existing closing instance.
+ */
+ private boolean shouldInheritExistingTaskBounds(
+ @Nullable ActivityRecord existingTaskActivity,
+ @Nullable ActivityRecord launchingActivity,
+ @NonNull Task launchingTask) {
+ if (existingTaskActivity == null || launchingActivity == null) return false;
+ return (existingTaskActivity.packageName == launchingActivity.packageName)
+ && isLaunchingNewTask(launchingActivity.launchMode,
+ launchingTask.getBaseIntent().getFlags())
+ && isClosingExitingInstance(launchingTask.getBaseIntent().getFlags());
+ }
+
+ /**
+ * Returns true if the launch mode or intent will result in a new task being created for the
+ * activity.
+ */
+ private boolean isLaunchingNewTask(int launchMode, int intentFlags) {
+ return launchMode == LAUNCH_SINGLE_TASK
+ || launchMode == LAUNCH_SINGLE_INSTANCE
+ || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK
+ || (intentFlags & FLAG_ACTIVITY_NEW_TASK) != 0;
+ }
+
+ /**
+ * Returns true if the intent will result in an existing task instance being closed if a new
+ * one appears.
+ */
+ private boolean isClosingExitingInstance(int intentFlags) {
+ return (intentFlags & FLAG_ACTIVITY_CLEAR_TASK) != 0
+ || (intentFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
+ }
+
private void initLogBuilder(Task task, ActivityRecord activity) {
if (DEBUG) {
mLogBuilder = new StringBuilder(
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 25fdf89afad1..2798e843d6dd 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -211,14 +211,17 @@ class Dimmer {
* child should call setAppearance again to request the Dim to continue.
* If multiple containers call this method, only the changes relative to the topmost will be
* applied.
+ * The creation of the dim layer is delayed if the requested dim and blur are 0.
* @param dimmingContainer Container requesting the dim
* @param alpha Dim amount
* @param blurRadius Blur amount
*/
protected void adjustAppearance(@NonNull WindowState dimmingContainer,
float alpha, int blurRadius) {
- final DimState d = obtainDimState(dimmingContainer);
- d.prepareLookChange(alpha, blurRadius);
+ if (mDimState != null || (alpha != 0 || blurRadius != 0)) {
+ final DimState d = obtainDimState(dimmingContainer);
+ d.prepareLookChange(alpha, blurRadius);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 574ab05075c4..59a042981375 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -41,6 +41,7 @@ import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_MASK;
import static android.util.TypedValue.COMPLEX_UNIT_SHIFT;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
@@ -82,7 +83,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
@@ -100,7 +100,6 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
-import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -108,7 +107,7 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_W
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
-import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
+import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS_IDENTIFIER;
import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
import static com.android.server.wm.DisplayContentProto.DISPLAY_READY;
@@ -118,9 +117,9 @@ import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
import static com.android.server.wm.DisplayContentProto.ID;
import static com.android.server.wm.DisplayContentProto.IME_POLICY;
-import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET;
-import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
-import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET_IDENTIFIER;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET_IDENTIFIER;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_LAYERING_TARGET_IDENTIFIER;
import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
@@ -156,7 +155,6 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.enablePersistingDensityScaleForConnectedDisplays;
import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import android.annotation.IntDef;
@@ -237,6 +235,7 @@ import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.inputmethod.ImeTracker;
+import android.window.DesktopExperienceFlags;
import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
@@ -248,6 +247,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -365,7 +365,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private boolean mTmpInitial;
private int mMaxUiWidth = 0;
- final AppTransition mAppTransition;
+ // TODO(b/400335290): extract the needed methods and remove this field.
+ final TransitionAnimation mTransitionAnimation;
final UnknownAppVisibilityController mUnknownAppVisibilityController;
@@ -1180,7 +1181,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mHoldScreenWakeLock.setReferenceCounted(false);
mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
- mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
+ mTransitionAnimation = new TransitionAnimation(mWmService.mContext, false /* debug */, TAG);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -2161,7 +2162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Re-show the previously hidden windows if all seamless rotated windows are done. */
void finishAsyncRotationIfPossible() {
final AsyncRotationController controller = mAsyncRotationController;
- if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
+ if (controller != null) {
controller.completeAll();
mAsyncRotationController = null;
}
@@ -2238,11 +2239,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private void applyRotation(final int oldRotation, final int rotation) {
mDisplayRotation.applyCurrentRotation(rotation);
- final boolean shellTransitions = mTransitionController.getTransitionPlayer() != null;
- final boolean rotateSeamlessly =
- mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
- final Transaction transaction =
- shellTransitions ? getSyncTransaction() : getPendingTransaction();
+
// We need to update our screen size information to match the new rotation. If the rotation
// has actually changed then this method will return true and, according to the comment at
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2250,25 +2247,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// #computeScreenConfiguration() later.
updateDisplayAndOrientation(null /* outConfig */);
- if (!shellTransitions) {
- forAllWindows(w -> {
- w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
- }, true /* traverseTopToBottom */);
- mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
- if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
- // Make sure DisplayRotation#isRotatingSeamlessly() will return false.
- mDisplayRotation.cancelSeamlessRotation();
- }
- }
+ // Before setDisplayProjection is applied by the start transaction of transition,
+ // set the transform hint to avoid using surface in old rotation.
+ setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
+ // The sync transaction should already contains setDisplayProjection, so unset the
+ // hint to restore the natural state when the transaction is applied.
+ getSyncTransaction().unsetFixedTransformHint(mSurfaceControl);
- if (shellTransitions) {
- // Before setDisplayProjection is applied by the start transaction of transition,
- // set the transform hint to avoid using surface in old rotation.
- setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
- // The sync transaction should already contains setDisplayProjection, so unset the
- // hint to restore the natural state when the transaction is applied.
- transaction.unsetFixedTransformHint(mSurfaceControl);
- }
scheduleAnimation();
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
@@ -2870,8 +2855,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// If the transition finished callback cannot match the token for some reason, make sure the
// rotated state is cleared if it is already invisible.
if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
- && !mFixedRotationLaunchingApp.isVisible()
- && !mDisplayRotation.isRotatingSeamlessly()) {
+ && !mFixedRotationLaunchingApp.isVisible()) {
clearFixedRotationLaunchingApp();
}
// If there won't be a transition to notify the launch is done, then it should be ready to
@@ -3154,7 +3138,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
// Update the base density if there is a forced density ratio.
- if (enablePersistingDensityScaleForConnectedDisplays()
+ if (DesktopExperienceFlags.ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS.isTrue()
&& mIsDensityForced && mExternalDisplayForcedDensityRatio != 0.0f) {
mBaseDisplayDensity = (int)
(mInitialDisplayDensity * mExternalDisplayForcedDensityRatio + 0.5);
@@ -3189,7 +3173,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
density = 0;
}
// Save the new density ratio to settings for external displays.
- if (enablePersistingDensityScaleForConnectedDisplays()
+ if (DesktopExperienceFlags.ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS.isTrue()
&& mDisplayInfo.type == TYPE_EXTERNAL) {
mExternalDisplayForcedDensityRatio = (float)
mBaseDisplayDensity / getInitialDisplayDensity();
@@ -3258,16 +3242,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
void onDisplayInfoChangeApplied() {
- if (!enableDisplayContentModeManagement()) {
+ if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
Slog.e(TAG, "ShouldShowSystemDecors shouldn't be updated when the flag is off.");
}
- final boolean shouldShowContent;
if (!allowContentModeSwitch()) {
return;
}
- shouldShowContent = mDisplay.canHostTasks();
+ final boolean shouldShowContent = mDisplay.canHostTasks();
if (shouldShowContent == mWmService.mDisplayWindowSettings
.shouldShowSystemDecorsLocked(this)) {
return;
@@ -3277,32 +3260,47 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!shouldShowContent) {
clearAllTasksOnDisplay(null /* clearTasksCallback */, false /* isRemovingDisplay */);
}
+
+ // If the display is allowed to show content, then it belongs to the display topology;
+ // vice versa.
+ mWmService.mDisplayManagerInternal.onDisplayBelongToTopologyChanged(mDisplayId,
+ /* inTopology= */ shouldShowContent);
}
- /**
- * Note that we only allow displays that are able to show system decorations to use the content
- * mode switch; however, not all displays that are able to show system decorations are allowed
- * to use the content mode switch.
- */
- boolean allowContentModeSwitch() {
+ /**
+ * Whether the display is allowed to switch the content mode between extended and mirroring.
+ * If the content mode is extended, the display will start home activity and show system
+ * decorations, such as wallpapaer, status bar and navigation bar.
+ * If the content mode is mirroring, the display will not show home activity or system
+ * decorations.
+ * The content mode is switched when {@link Display#canHostTasks()} changes.
+ *
+ * Note that we only allow displays that are able to show system decorations to use the content
+ * mode switch; however, not all displays that are able to show system decorations are allowed
+ * to use the content mode switch.
+ */
+ boolean allowContentModeSwitch() {
+ if ((mDisplay.getFlags() & FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0) {
+ return false;
+ }
+
// The default display should always show system decorations.
if (isDefaultDisplay) {
return false;
}
- // Private display should never show system decorations.
- if (isPrivate()) {
+ // Private or untrusted display should never show system decorations.
+ if (isPrivate() || !isTrusted()) {
return false;
}
- // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH.
- if ((mDisplay.getFlags() & Display.FLAG_REAR) != 0) {
+ if (shouldNeverShowSystemDecorations()) {
return false;
}
- // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH.
- // Virtual displays cannot add or remove system decorations during their lifecycle.
- if (mDisplay.getType() == Display.TYPE_VIRTUAL) {
+ // Display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled should always show system
+ // decorations, and should not switch the content mode.
+ if ((mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
return false;
}
@@ -3612,18 +3610,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
if (mImeLayeringTarget != null) {
- mImeLayeringTarget.dumpDebug(proto, INPUT_METHOD_TARGET, logLevel);
+ mImeLayeringTarget.writeIdentifierToProto(
+ proto, INPUT_METHOD_LAYERING_TARGET_IDENTIFIER);
}
- if (mImeInputTarget != null) {
- mImeInputTarget.dumpProto(proto, INPUT_METHOD_INPUT_TARGET, logLevel);
+ if (mImeInputTarget != null && mImeInputTarget.getWindowState() != null) {
+ mImeInputTarget.getWindowState().writeIdentifierToProto(
+ proto, INPUT_METHOD_INPUT_TARGET_IDENTIFIER);
}
if (mImeControlTarget != null
&& mImeControlTarget.getWindow() != null) {
- mImeControlTarget.getWindow().dumpDebug(proto, INPUT_METHOD_CONTROL_TARGET,
- logLevel);
+ mImeControlTarget.getWindow().writeIdentifierToProto(
+ proto, INPUT_METHOD_CONTROL_TARGET_IDENTIFIER);
}
if (mCurrentFocus != null) {
- mCurrentFocus.dumpDebug(proto, CURRENT_FOCUS, logLevel);
+ mCurrentFocus.writeIdentifierToProto(proto, CURRENT_FOCUS_IDENTIFIER);
}
if (mInsetsStateController != null) {
mInsetsStateController.dumpDebug(proto, logLevel);
@@ -5055,7 +5055,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
- if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) {
+ if (!inTransition()) {
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
@@ -5634,29 +5634,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
- */
- @Deprecated
- void prepareAppTransition(@WindowManager.TransitionType int transit) {
- prepareAppTransition(transit, 0 /* flags */);
- }
-
- /**
- * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
- */
- @Deprecated
- void prepareAppTransition(@WindowManager.TransitionType int transit,
- @WindowManager.TransitionFlags int flags) {
- mAppTransition.prepareAppTransition(transit, flags);
- }
-
- /**
* Helper that both requests a transition (using the new transition system) and prepares
* the legacy transition system. Use this when both systems have the same start-point.
*
* @see TransitionController#requestTransitionIfNeeded(int, int, WindowContainer,
* WindowContainer)
- * @see AppTransition#prepareAppTransition
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
@@ -5675,16 +5657,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
}
- /**
- * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
- */
- boolean isSystemDecorationsSupported() {
+ private boolean shouldNeverShowSystemDecorations() {
if (mDisplayId == mWmService.mVr2dDisplayId) {
// VR virtual display will be used to run and render 2D app within a VR experience.
- return false;
+ return true;
}
if (!isTrusted()) {
// Do not show system decorations on untrusted virtual display.
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
+ */
+ boolean isSystemDecorationsSupported() {
+ if (shouldNeverShowSystemDecorations()) {
return false;
}
if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
@@ -5692,7 +5681,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// This display is configured to show system decorations.
return true;
}
- if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
+ if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()
+ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
// System decorations should not be forced on a rear display due to security
// policies.
@@ -6544,19 +6534,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// If keyguard is not locked, the change of flags won't affect activity visibility.
return;
}
- // We might change the visibilities here, so prepare an empty app transition which might be
- // overridden later if we actually change visibilities.
- final boolean wasTransitionSet = mAppTransition.isTransitionSet();
- if (!wasTransitionSet) {
- prepareAppTransition(TRANSIT_NONE);
- }
mRootWindowContainer.ensureActivitiesVisible();
-
- // If there was a transition set already we don't want to interfere with it as we might be
- // starting it too early.
- if (!wasTransitionSet) {
- executeAppTransition();
- }
+ // In case there is a visibility change.
+ executeAppTransition();
}
/**
@@ -7108,6 +7088,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
public void setAnimatingTypes(@InsetsType int animatingTypes) {
if (mAnimatingTypes != animatingTypes) {
mAnimatingTypes = animatingTypes;
+
+ if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+ getInsetsStateController().onAnimatingTypesChanged(this);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ec5b503fbb9b..313b77ed4b20 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -67,7 +67,6 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
-import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
@@ -107,6 +106,7 @@ import android.view.InsetsFlags;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.PrivacyIndicatorBounds;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
@@ -119,6 +119,7 @@ import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.window.ClientWindowFrames;
+import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import com.android.internal.R;
@@ -747,7 +748,7 @@ public class DisplayPolicy {
}
void updateHasNavigationBarIfNeeded() {
- if (!enableDisplayContentModeManagement()) {
+ if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
Slog.e(TAG, "mHasNavigationBar shouldn't be updated when the flag is off.");
}
@@ -1875,7 +1876,7 @@ public class DisplayPolicy {
}
void notifyDisplayAddSystemDecorations() {
- if (enableDisplayContentModeManagement()) {
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
final int displayId = getDisplayId();
final boolean isSystemDecorationsSupported =
mDisplayContent.isSystemDecorationsSupported();
@@ -2121,6 +2122,8 @@ public class DisplayPolicy {
}
private static class Cache {
+ static final int TYPE_REGULAR_BARS = WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
/**
* If {@link #mPreserveId} is this value, it is in the middle of updating display
* configuration before a transition is started. Then the active cache should be used.
@@ -2130,6 +2133,14 @@ public class DisplayPolicy {
int mPreserveId;
boolean mActive;
+ /**
+ * When display switches, mRegularBarsInsets will assign to mPreservedInsets, and the
+ * insets sources of previous device state will copy to mRegularBarsInsets.
+ */
+ ArrayList<InsetsSource> mPreservedInsets;
+ ArrayList<InsetsSource> mRegularBarsInsets;
+ PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+
Cache(DisplayContent dc) {
mDecorInsets = new DecorInsets(dc);
}
@@ -2138,6 +2149,17 @@ public class DisplayPolicy {
return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent
.mTransitionController.inTransition(mPreserveId);
}
+
+ static ArrayList<InsetsSource> copyRegularBarInsets(InsetsState srcState) {
+ final ArrayList<InsetsSource> state = new ArrayList<>();
+ for (int i = srcState.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = srcState.sourceAt(i);
+ if ((source.getType() & TYPE_REGULAR_BARS) != 0) {
+ state.add(new InsetsSource(source));
+ }
+ }
+ return state;
+ }
}
}
@@ -2213,24 +2235,60 @@ public class DisplayPolicy {
@VisibleForTesting
void updateCachedDecorInsets() {
DecorInsets prevCache = null;
+ PrivacyIndicatorBounds privacyIndicatorBounds = null;
if (mCachedDecorInsets == null) {
mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent);
} else {
prevCache = new DecorInsets(mDisplayContent);
prevCache.setTo(mCachedDecorInsets.mDecorInsets);
+ privacyIndicatorBounds = mCachedDecorInsets.mPrivacyIndicatorBounds;
+ mCachedDecorInsets.mPreservedInsets = mCachedDecorInsets.mRegularBarsInsets;
}
// Set a special id to preserve it before a real id is available from transition.
mCachedDecorInsets.mPreserveId = DecorInsets.Cache.ID_UPDATING_CONFIG;
// Cache the current insets.
mCachedDecorInsets.mDecorInsets.setTo(mDecorInsets);
+ if (com.android.window.flags.Flags.useCachedInsetsForDisplaySwitch()) {
+ mCachedDecorInsets.mRegularBarsInsets = DecorInsets.Cache.copyRegularBarInsets(
+ mDisplayContent.mDisplayFrames.mInsetsState);
+ mCachedDecorInsets.mPrivacyIndicatorBounds =
+ mDisplayContent.mCurrentPrivacyIndicatorBounds;
+ } else {
+ mCachedDecorInsets.mRegularBarsInsets = null;
+ mCachedDecorInsets.mPrivacyIndicatorBounds = null;
+ }
// Switch current to previous cache.
if (prevCache != null) {
mDecorInsets.setTo(prevCache);
+ if (privacyIndicatorBounds != null) {
+ mDisplayContent.mCurrentPrivacyIndicatorBounds = privacyIndicatorBounds;
+ }
mCachedDecorInsets.mActive = true;
}
}
/**
+ * This returns a new InsetsState with replacing the insets in target device state when the
+ * display is switching (e.g. fold/unfold). Otherwise, it returns the original state. This is
+ * to avoid dispatching old insets source before the insets providers update new insets.
+ */
+ InsetsState replaceInsetsSourcesIfNeeded(InsetsState originalState, boolean copyState) {
+ if (mCachedDecorInsets == null || mCachedDecorInsets.mPreservedInsets == null
+ || !shouldKeepCurrentDecorInsets()) {
+ return originalState;
+ }
+ final ArrayList<InsetsSource> preservedSources = mCachedDecorInsets.mPreservedInsets;
+ final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
+ for (int i = preservedSources.size() - 1; i >= 0; i--) {
+ final InsetsSource cacheSource = preservedSources.get(i);
+ if (state.peekSource(cacheSource.getId()) != null) {
+ state.addSource(new InsetsSource(cacheSource));
+ }
+ }
+ return state;
+ }
+
+ /**
* Called after the display configuration is updated according to the physical change. Suppose
* there should be a display change transition, so associate the cached decor insets with the
* transition to limit the lifetime of using the cache.
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 9cf792d82f56..9edbb70c3b74 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -168,19 +168,6 @@ public class DisplayRotation {
private int mDeferredRotationPauseCount;
/**
- * A count of the windows which are 'seamlessly rotated', e.g. a surface at an old orientation
- * is being transformed. We freeze orientation updates while any windows are seamlessly rotated,
- * so we need to track when this hits zero so we can apply deferred orientation updates.
- */
- private int mSeamlessRotationCount;
-
- /**
- * True in the interval from starting seamless rotation until the last rotated window draws in
- * the new orientation.
- */
- private boolean mRotatingSeamlessly;
-
- /**
* Behavior of rotation suggestions.
*
* @see Settings.Secure#SHOW_ROTATION_SUGGESTIONS
@@ -630,15 +617,6 @@ public class DisplayRotation {
return true;
}
- if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
- // The screen rotation animation uses a screenshot to freeze the screen while windows
- // resize underneath. When we are rotating seamlessly, we allow the elements to
- // transition to their rotated state independently and without a freeze required.
- prepareSeamlessRotation();
- } else {
- cancelSeamlessRotation();
- }
-
// Give a remote handler (system ui) some time to reposition things.
startRemoteRotation(oldRotation, mRotation);
@@ -677,42 +655,6 @@ public class DisplayRotation {
}
}
- /**
- * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
- * set by previous {@link #updateRotationUnchecked}, but another orientation change happens
- * before calling {@link DisplayContent#sendNewConfiguration} (remote rotation hasn't finished)
- * and it doesn't choose seamless rotation.
- */
- void cancelSeamlessRotation() {
- if (!mRotatingSeamlessly) {
- return;
- }
- mDisplayContent.forAllWindows(w -> {
- if (w.mSeamlesslyRotated) {
- w.cancelSeamlessRotation();
- w.mSeamlesslyRotated = false;
- }
- }, true /* traverseTopToBottom */);
- mSeamlessRotationCount = 0;
- mRotatingSeamlessly = false;
- mDisplayContent.finishAsyncRotationIfPossible();
- }
-
- private void prepareSeamlessRotation() {
- // We are careful to reset this in case a window was removed before it finished
- // seamless rotation.
- mSeamlessRotationCount = 0;
- mRotatingSeamlessly = true;
- }
-
- boolean isRotatingSeamlessly() {
- return mRotatingSeamlessly;
- }
-
- boolean hasSeamlessRotatingWindow() {
- return mSeamlessRotationCount > 0;
- }
-
@VisibleForTesting
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
@@ -750,13 +692,6 @@ public class DisplayRotation {
return false;
}
- // We can't rotate (seamlessly or not) while waiting for the last seamless rotation to
- // complete (that is, waiting for windows to redraw). It's tempting to check
- // mSeamlessRotationCount but that could be incorrect in the case of window-removal.
- if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
- return false;
- }
-
return true;
}
@@ -774,28 +709,6 @@ public class DisplayRotation {
return oldRotation != Surface.ROTATION_180 && newRotation != Surface.ROTATION_180;
}
- void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
- if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
- return;
- }
-
- w.mSeamlesslyRotated = seamlesslyRotated;
- if (seamlesslyRotated) {
- mSeamlessRotationCount++;
- } else {
- mSeamlessRotationCount--;
- }
- if (mSeamlessRotationCount == 0) {
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Performing post-rotate rotation after seamless rotation");
- // Finish seamless rotation.
- mRotatingSeamlessly = false;
- mDisplayContent.finishAsyncRotationIfPossible();
-
- updateRotationAndSendNewConfigIfChanged();
- }
- }
-
void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
mFixedToUserRotation = fixedToUserRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index fa7a99d55896..d90fff229cd9 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -40,14 +40,14 @@ class DisplayWindowListenerController {
}
int[] registerListener(IDisplayWindowListener listener) {
+ mDisplayListeners.register(listener);
+ final IntArray displayIds = new IntArray();
synchronized (mService.mGlobalLock) {
- mDisplayListeners.register(listener);
- final IntArray displayIds = new IntArray();
mService.mAtmService.mRootWindowContainer.forAllDisplays((displayContent) -> {
displayIds.add(displayContent.mDisplayId);
});
- return displayIds.toArray();
}
+ return displayIds.toArray();
}
void unregisterListener(IDisplayWindowListener listener) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c6892e941fc6..56579206566f 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -23,7 +23,6 @@ import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
-import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO;
import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED;
@@ -37,6 +36,7 @@ import android.view.IWindowManager;
import android.view.Surface;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
+import android.window.DesktopExperienceFlags;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.DisplayContent.ForceScalingMode;
@@ -240,6 +240,11 @@ class DisplayWindowSettings {
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
+ /**
+ * Returns {@code true} if either the display is the default display, or the display is allowed
+ * to dynamically add/remove system decorations and the system decorations should be shown on it
+ * currently.
+ */
boolean shouldShowSystemDecorsLocked(@NonNull DisplayContent dc) {
if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
// Default display should show system decors.
@@ -255,7 +260,7 @@ class DisplayWindowSettings {
final boolean changed = (shouldShow != shouldShowSystemDecorsLocked(dc));
setShouldShowSystemDecorsInternalLocked(dc, shouldShow);
- if (enableDisplayContentModeManagement()) {
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
if (dc.isDefaultDisplay || dc.isPrivate() || !changed) {
return;
}
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index a017a1173d97..e508a6d23178 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -23,8 +23,6 @@ import static com.android.server.wm.Task.TAG_VISIBILITY;
import android.annotation.Nullable;
import android.util.Slog;
-import com.android.window.flags.Flags;
-
import java.util.ArrayList;
/** Helper class to ensure activities are in the right visible state for a container. */
@@ -112,18 +110,11 @@ class EnsureActivitiesVisibleHelper {
if (adjacentTaskFragments != null && adjacentTaskFragments.contains(
childTaskFragment)) {
- final boolean isTranslucent;
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- isTranslucent = childTaskFragment.isTranslucent(starting)
- || childTaskFragment.forOtherAdjacentTaskFragments(
- adjacentTaskFragment -> {
- return adjacentTaskFragment.isTranslucent(starting);
- });
- } else {
- isTranslucent = childTaskFragment.isTranslucent(starting)
- || childTaskFragment.getAdjacentTaskFragment()
- .isTranslucent(starting);
- }
+ final boolean isTranslucent = childTaskFragment.isTranslucent(starting)
+ || childTaskFragment.forOtherAdjacentTaskFragments(
+ adjacentTaskFragment -> {
+ return adjacentTaskFragment.isTranslucent(starting);
+ });
if (!isTranslucent) {
// Everything behind two adjacent TaskFragments are occluded.
mBehindFullyOccludedContainer = true;
@@ -135,14 +126,10 @@ class EnsureActivitiesVisibleHelper {
if (adjacentTaskFragments == null) {
adjacentTaskFragments = new ArrayList<>();
}
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments;
- childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
- adjacentTfs.add(adjacentTf);
- });
- } else {
- adjacentTaskFragments.add(childTaskFragment.getAdjacentTaskFragment());
- }
+ final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments;
+ childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+ adjacentTfs.add(adjacentTf);
+ });
}
} else if (child.asActivityRecord() != null) {
setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index f52446ff494c..040bbe46c3aa 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -22,7 +22,7 @@ import static android.view.InsetsSource.ID_IME;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
+import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME_IDENTIFIER;
import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
@@ -104,7 +104,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending
&& mControlTarget != null) {
ProtoLog.d(WM_DEBUG_IME,
- "onPostLayout: IME control ready to be dispatched, ws=%s", ws);
+ "onPostLayout: IME control ready to be dispatched, controlTarget=%s",
+ mControlTarget);
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
@@ -115,13 +116,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// If the server visibility didn't change (still visible), and mGivenInsetsReady
// is set, we won't call into notifyControlChanged. Therefore, we can reset the
// statsToken, if available.
- ProtoLog.w(WM_DEBUG_IME, "onPostLayout cancel statsToken, ws=%s", ws);
+ ProtoLog.w(WM_DEBUG_IME, "onPostLayout cancel statsToken, controlTarget=%s",
+ mControlTarget);
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
} else if (wasServerVisible && !isServerVisible()) {
- ProtoLog.d(WM_DEBUG_IME, "onPostLayout: setImeShowing(false) was: %s, ws=%s",
- isImeShowing(), ws);
+ ProtoLog.d(WM_DEBUG_IME,
+ "onPostLayout: setImeShowing(false) was: %s, controlTarget=%s",
+ isImeShowing(), mControlTarget);
setImeShowing(false);
}
}
@@ -260,8 +263,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
boolean oldVisibility = mSource.isVisible();
super.updateVisibility();
if (Flags.refactorInsetsController()) {
- if (mSource.isVisible() && !oldVisibility && mImeRequester != null) {
- reportImeDrawnForOrganizerIfNeeded(mImeRequester);
+ if (mSource.isVisible() && !oldVisibility && mControlTarget != null) {
+ reportImeDrawnForOrganizerIfNeeded(mControlTarget);
}
}
onSourceChanged();
@@ -285,11 +288,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// If insets target is not available (e.g. RemoteInsetsControlTarget), use current
// IME input target to update IME request state. For example, switch from a task
// with showing IME to a split-screen task without showing IME.
- InsetsTarget insetsTarget = target.getWindow();
- if (insetsTarget == null && mServerVisible) {
- insetsTarget = mDisplayContent.getImeInputTarget();
+ InputTarget imeInputTarget = mDisplayContent.getImeInputTarget();
+ if (imeInputTarget != target && imeInputTarget != null) {
+ // The controlTarget should be updated with the visibility of the
+ // current IME input target.
+ reportImeInputTargetStateToControlTarget(imeInputTarget, target,
+ statsToken);
+ } else {
+ invokeOnImeRequestedChangedListener(target, statsToken);
}
- invokeOnImeRequestedChangedListener(insetsTarget, statsToken);
}
}
}
@@ -325,8 +332,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (changed) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
- invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
- statsToken);
+ invokeOnImeRequestedChangedListener(controlTarget, statsToken);
} else {
// TODO(b/353463205) check cancelled / failed
ImeTracker.forLogging().onCancelled(statsToken,
@@ -380,7 +386,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// not all virtual displays have an ImeInsetsSourceProvider, so it is not
// guaranteed that the IME will be started when the control target reports its
// requested visibility back. Thus, invoking the listener here.
- invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
+ invokeOnImeRequestedChangedListener((InsetsControlTarget) imeInsetsTarget,
+ statsToken);
} else {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
@@ -389,18 +396,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
}
// TODO(b/353463205) check callers to see if we can make statsToken @NonNull
- private void invokeOnImeRequestedChangedListener(InsetsTarget insetsTarget,
+ private void invokeOnImeRequestedChangedListener(InsetsControlTarget controlTarget,
@Nullable ImeTracker.Token statsToken) {
final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener;
if (imeListener != null) {
- if (insetsTarget != null) {
+ if (controlTarget != null) {
+ final boolean imeAnimating = Flags.reportAnimatingInsetsTypes()
+ && (controlTarget.getAnimatingTypes() & WindowInsets.Type.ime()) != 0;
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY);
mDisplayContent.mWmService.mH.post(() -> {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER);
- imeListener.onImeRequestedChanged(insetsTarget.getWindowToken(),
- insetsTarget.isRequestedVisible(WindowInsets.Type.ime()), statsToken);
+ imeListener.onImeRequestedChanged(controlTarget.getWindowToken(),
+ controlTarget.isRequestedVisible(WindowInsets.Type.ime())
+ || imeAnimating, statsToken);
});
} else {
ImeTracker.forLogging().onFailed(statsToken,
@@ -413,6 +423,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
}
}
+ @Override
+ void onAnimatingTypesChanged(InsetsControlTarget caller) {
+ if (Flags.reportAnimatingInsetsTypes()) {
+ final InsetsControlTarget controlTarget = getControlTarget();
+ // If the IME is not being requested anymore and the animation is finished, we need to
+ // invoke the listener, to let IMS eventually know
+ if (caller != null && caller == controlTarget && !caller.isRequestedVisible(
+ WindowInsets.Type.ime())
+ && (caller.getAnimatingTypes() & WindowInsets.Type.ime()) == 0) {
+ // TODO(b/353463205) check statsToken
+ invokeOnImeRequestedChangedListener(caller, null);
+ }
+ }
+ }
+
private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) {
final WindowState callerWindow = caller.getWindow();
if (callerWindow == null) {
@@ -774,7 +799,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
final WindowState imeRequesterWindow =
mImeRequester != null ? mImeRequester.getWindow() : null;
if (imeRequesterWindow != null) {
- imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
+ imeRequesterWindow.writeIdentifierToProto(proto, IME_TARGET_FROM_IME_IDENTIFIER);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 7751ac3f9fc6..a4bc5cbcb5d3 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -343,6 +343,13 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
}
}
+ @Override
+ public boolean isKeyguardLocked(int displayId) {
+ synchronized (mService.mGlobalLock) {
+ return mService.mAtmService.mKeyguardController.isKeyguardLocked(displayId);
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 28722141dcd3..06754c4517ab 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -229,6 +229,7 @@ class InsetsPolicy {
state = originalState;
}
state = adjustVisibilityForIme(target, state, state == originalState);
+ state = mPolicy.replaceInsetsSourcesIfNeeded(state, state == originalState);
return adjustInsetsForRoundedCorners(target.mToken, state, state == originalState);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d1585d06ae40..1b693fc05b21 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -21,16 +21,16 @@ import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.CONTROL;
import static com.android.server.wm.InsetsSourceProviderProto.CONTROLLABLE;
-import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET_IDENTIFIER;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
-import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET_IDENTIFIER;
import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
-import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET_IDENTIFIER;
import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
-import static com.android.server.wm.InsetsSourceProviderProto.SOURCE_WINDOW_STATE;
+import static com.android.server.wm.InsetsSourceProviderProto.SOURCE_WINDOW_STATE_IDENTIFIER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
import android.annotation.NonNull;
@@ -673,6 +673,9 @@ class InsetsSourceProvider {
mServerVisible, mClientVisible);
}
+ void onAnimatingTypesChanged(InsetsControlTarget caller) {
+ }
+
protected boolean isLeashReadyForDispatching() {
return isLeashInitialized();
}
@@ -803,14 +806,16 @@ class InsetsSourceProvider {
mControl.dumpDebug(proto, CONTROL);
}
if (mControlTarget != null && mControlTarget.getWindow() != null) {
- mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
+ mControlTarget.getWindow().writeIdentifierToProto(proto, CONTROL_TARGET_IDENTIFIER);
}
if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget
&& mPendingControlTarget.getWindow() != null) {
- mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
+ mPendingControlTarget.getWindow().writeIdentifierToProto(
+ proto, PENDING_CONTROL_TARGET_IDENTIFIER);
}
if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
- mFakeControlTarget.getWindow().dumpDebug(proto, FAKE_CONTROL_TARGET, logLevel);
+ mFakeControlTarget.getWindow().writeIdentifierToProto(
+ proto, FAKE_CONTROL_TARGET_IDENTIFIER);
}
if (mAdapter != null && mAdapter.mCapturedLeash != null) {
mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH);
@@ -821,7 +826,8 @@ class InsetsSourceProvider {
proto.write(SEAMLESS_ROTATING, mSeamlessRotating);
proto.write(CONTROLLABLE, mControllable);
if (mWindowContainer != null && mWindowContainer.asWindowState() != null) {
- mWindowContainer.asWindowState().dumpDebug(proto, SOURCE_WINDOW_STATE, logLevel);
+ mWindowContainer.asWindowState().writeIdentifierToProto(
+ proto, SOURCE_WINDOW_STATE_IDENTIFIER);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5e0395f70e65..810e48f492e1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -393,6 +393,13 @@ class InsetsStateController {
}
}
+ void onAnimatingTypesChanged(InsetsControlTarget target) {
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
+ provider.onAnimatingTypesChanged(target);
+ }
+ }
+
private void notifyPendingInsetsControlChanged() {
if (mPendingTargetProvidersMap.isEmpty()) {
return;
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 4eeed5ec8423..ff1e400a4a23 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHA
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -310,7 +309,6 @@ class KeyguardController {
state.writeEventLog("keyguardGoingAway");
final int transitFlags = convertTransitFlags(flags);
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
- dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
// We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
// TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
// away.
@@ -490,8 +488,6 @@ class KeyguardController {
if (trigger != null) {
transition.collect(trigger);
}
- } else {
- dc.prepareAppTransition(transitType, transitFlags);
}
} else {
if (tc.inTransition()) {
@@ -515,7 +511,6 @@ class KeyguardController {
private void handleDismissInsecureKeyguard(DisplayContent dc) {
mService.deferWindowLayout();
try {
- dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, 0 /* transitFlags */);
// We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
// TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
// away.
@@ -542,14 +537,6 @@ class KeyguardController {
mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
final KeyguardDisplayState state = getDisplayState(displayId);
state.mDismissalRequested = true;
-
- // If we are about to unocclude the Keyguard, but we can dismiss it without security,
- // we immediately dismiss the Keyguard so the activity gets shown without a flicker.
- final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
- if (state.mKeyguardShowing && canDismissKeyguard()
- && dc.mAppTransition.containsTransitRequest(TRANSIT_KEYGUARD_UNOCCLUDE)) {
- mWindowManager.executeAppTransition();
- }
}
ActivityRecord getTopOccludingActivity(int displayId) {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index fa65bda7104d..2406178e46fe 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -26,6 +26,7 @@ import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration.WindowingMode;
@@ -36,6 +37,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between
@@ -96,18 +98,18 @@ class LaunchParamsController {
mTmpResult.reset();
final LaunchParamsModifier modifier = mModifiers.get(i);
- switch(modifier.onCalculate(task, layout, activity, source, options, request, phase,
+ switch (modifier.onCalculate(task, layout, activity, source, options, request, phase,
mTmpCurrent, mTmpResult)) {
case RESULT_SKIP:
// Do not apply any results when we are told to skip
continue;
case RESULT_DONE:
// Set result and return immediately.
- result.set(mTmpResult);
+ result.merge(mTmpResult);
return;
case RESULT_CONTINUE:
// Set result and continue
- result.set(mTmpResult);
+ result.merge(mTmpResult);
break;
}
}
@@ -138,6 +140,10 @@ class LaunchParamsController {
mService.deferWindowLayout();
try {
if (task.getRootTask().inMultiWindowMode()) {
+ if (!mTmpParams.mAppBounds.isEmpty()) {
+ task.getRequestedOverrideConfiguration().windowConfiguration.setAppBounds(
+ mTmpParams.mAppBounds);
+ }
task.setBounds(mTmpParams.mBounds);
return true;
}
@@ -168,7 +174,11 @@ class LaunchParamsController {
*/
static class LaunchParams {
/** The bounds within the parent container. */
+ @NonNull
final Rect mBounds = new Rect();
+ /** The bounds within the parent container respecting insets. Usually empty. */
+ @NonNull
+ final Rect mAppBounds = new Rect();
/** The display area the {@link Task} would prefer to be on. */
@Nullable
@@ -178,24 +188,45 @@ class LaunchParamsController {
@WindowingMode
int mWindowingMode;
+ /** Whether the Activity needs the safe region bounds. A {@code null} value means unset. */
+ @Nullable
+ Boolean mNeedsSafeRegionBounds = null;
+
/** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */
void reset() {
mBounds.setEmpty();
+ mAppBounds.setEmpty();
mPreferredTaskDisplayArea = null;
mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ mNeedsSafeRegionBounds = null;
}
/** Copies the values set on the passed in {@link LaunchParams}. */
void set(LaunchParams params) {
mBounds.set(params.mBounds);
+ mAppBounds.set(params.mAppBounds);
+ mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea;
+ mWindowingMode = params.mWindowingMode;
+ mNeedsSafeRegionBounds = params.mNeedsSafeRegionBounds;
+ }
+
+ /** Merges the values set on the passed in {@link LaunchParams}. */
+ void merge(LaunchParams params) {
+ mBounds.set(params.mBounds);
+ mAppBounds.set(params.mAppBounds);
mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea;
mWindowingMode = params.mWindowingMode;
+ // Only update mNeedsSafeRegionBounds if a modifier updates it by setting a non null
+ // value. Otherwise, carry over from previous modifiers
+ if (params.mNeedsSafeRegionBounds != null) {
+ mNeedsSafeRegionBounds = params.mNeedsSafeRegionBounds;
+ }
}
/** Returns {@code true} if no values have been explicitly set. */
boolean isEmpty() {
- return mBounds.isEmpty() && mPreferredTaskDisplayArea == null
- && mWindowingMode == WINDOWING_MODE_UNDEFINED;
+ return mBounds.isEmpty() && mAppBounds.isEmpty() && mPreferredTaskDisplayArea == null
+ && mWindowingMode == WINDOWING_MODE_UNDEFINED && mNeedsSafeRegionBounds == null;
}
boolean hasWindowingMode() {
@@ -215,15 +246,20 @@ class LaunchParamsController {
if (mPreferredTaskDisplayArea != that.mPreferredTaskDisplayArea) return false;
if (mWindowingMode != that.mWindowingMode) return false;
- return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null;
+ if (!mAppBounds.equals(that.mAppBounds)) return false;
+ if (!Objects.equals(mNeedsSafeRegionBounds, that.mNeedsSafeRegionBounds)) return false;
+ return !mBounds.isEmpty() ? mBounds.equals(that.mBounds) : that.mBounds.isEmpty();
}
@Override
public int hashCode() {
- int result = mBounds != null ? mBounds.hashCode() : 0;
+ int result = !mBounds.isEmpty() ? mBounds.hashCode() : 0;
+ result = 31 * result + mAppBounds.hashCode();
result = 31 * result + (mPreferredTaskDisplayArea != null
? mPreferredTaskDisplayArea.hashCode() : 0);
result = 31 * result + mWindowingMode;
+ result = 31 * result + (mNeedsSafeRegionBounds != null
+ ? Boolean.hashCode(mNeedsSafeRegionBounds) : 0);
return result;
}
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 29c0c7b2035e..3a4d2ca8b9bd 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -54,7 +54,6 @@ public class Letterbox {
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
- private final Supplier<SurfaceControl> mParentSurfaceSupplier;
private final Rect mOuter = new Rect();
private final Rect mInner = new Rect();
@@ -83,13 +82,11 @@ public class Letterbox {
public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@NonNull AppCompatReachabilityPolicy appCompatReachabilityPolicy,
- @NonNull AppCompatLetterboxOverrides appCompatLetterboxOverrides,
- Supplier<SurfaceControl> parentSurface) {
+ @NonNull AppCompatLetterboxOverrides appCompatLetterboxOverrides) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAppCompatReachabilityPolicy = appCompatReachabilityPolicy;
mAppCompatLetterboxOverrides = appCompatLetterboxOverrides;
- mParentSurfaceSupplier = parentSurface;
}
/**
@@ -343,7 +340,6 @@ public class Letterbox {
private SurfaceControl mInputSurface;
private Color mColor;
private boolean mHasWallpaperBackground;
- private SurfaceControl mParentSurface;
private final Rect mSurfaceFrameRelative = new Rect();
private final Rect mLayoutFrameGlobal = new Rect();
@@ -437,9 +433,8 @@ public class Letterbox {
}
mColor = mAppCompatLetterboxOverrides.getLetterboxBackgroundColor();
- mParentSurface = mParentSurfaceSupplier.get();
t.setColor(mSurface, getRgbColorArray());
- setPositionAndReparent(t, mSurface);
+ setPositionAndCrop(t, mSurface);
mHasWallpaperBackground = mAppCompatLetterboxOverrides
.hasWallpaperBackgroundForLetterbox();
@@ -448,7 +443,7 @@ public class Letterbox {
t.show(mSurface);
if (mInputSurface != null) {
- setPositionAndReparent(inputT, mInputSurface);
+ setPositionAndCrop(inputT, mInputSurface);
inputT.setTrustedOverlay(mInputSurface, true);
inputT.show(mInputSurface);
}
@@ -470,12 +465,11 @@ public class Letterbox {
}
}
- private void setPositionAndReparent(@NonNull SurfaceControl.Transaction t,
+ private void setPositionAndCrop(@NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl surface) {
t.setPosition(surface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(surface, mSurfaceFrameRelative.width(),
mSurfaceFrameRelative.height());
- t.reparent(surface, mParentSurface);
}
private void updateAlphaAndBlur(SurfaceControl.Transaction t) {
@@ -511,14 +505,13 @@ public class Letterbox {
public boolean needsApplySurfaceChanges() {
return !mSurfaceFrameRelative.equals(mLayoutFrameRelative)
- // If mSurfaceFrameRelative is empty then mHasWallpaperBackground, mColor,
- // and mParentSurface may never be updated in applySurfaceChanges but this
- // doesn't mean that update is needed.
+ // If mSurfaceFrameRelative is empty, then mHasWallpaperBackground and mColor
+ // may never be updated in applySurfaceChanges but this doesn't mean that
+ // update is needed.
|| !mSurfaceFrameRelative.isEmpty()
&& (mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox()
!= mHasWallpaperBackground
- || !mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().equals(mColor)
- || mParentSurfaceSupplier.get() != mParentSurface);
+ || !mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().equals(mColor));
}
}
}
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 6dd7d35856df..6e59828c8ff2 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -21,20 +21,13 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import android.app.PictureInPictureParams;
import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.util.RotationUtils;
import android.util.Slog;
import android.view.IPinnedTaskListener;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.window.PictureInPictureSurfaceTransaction;
import java.io.PrintWriter;
@@ -71,11 +64,7 @@ class PinnedTaskController {
* based on the new rotation.
*/
private Rect mDestRotatedBounds;
- /**
- * Non-null if the entering PiP task from recents animation will cause display rotation to
- * change. The transaction is based on the old rotation.
- */
- private PictureInPictureSurfaceTransaction mPipTransaction;
+
/** Whether to skip task configuration change once. */
private boolean mFreezingTaskConfig;
/** Defer display orientation change if the PiP task is animating across orientations. */
@@ -212,14 +201,12 @@ class PinnedTaskController {
}
/**
- * Sets the transaction for {@link #startSeamlessRotationIfNeeded} if the orientation of display
- * will be changed. This is only called when finishing recents animation with pending
- * orientation change that will be handled by
- * {@link DisplayContent.FixedRotationTransitionListener#onFinishRecentsAnimation}.
+ * Sets a hint if the orientation of display will be changed. This is only called when
+ * finishing recents animation with pending orientation change that will be handled by
+ * {@link DisplayContent.FixedRotationTransitionListener}.
*/
- void setEnterPipTransaction(PictureInPictureSurfaceTransaction tx) {
+ void setEnterPipWithRotatedTransientLaunch() {
mFreezingTaskConfig = true;
- mPipTransaction = tx;
}
/** Called when the activity in PiP task has PiP windowing mode (at the end of animation). */
@@ -233,81 +220,6 @@ class PinnedTaskController {
}
/**
- * Resets rotation and applies scale and position to PiP task surface to match the current
- * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
- * receives the callback of fixed rotation completion.
- */
- void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t,
- int oldRotation, int newRotation) {
- final Rect bounds = mDestRotatedBounds;
- final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
- final boolean emptyPipPositionTx = pipTx == null || pipTx.mPosition == null;
- if (bounds == null && emptyPipPositionTx) {
- return;
- }
- final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea();
- final Task pinnedTask = taskArea.getRootPinnedTask();
- if (pinnedTask == null) {
- return;
- }
-
- mDestRotatedBounds = null;
- mPipTransaction = null;
- final Rect areaBounds = taskArea.getBounds();
- if (!emptyPipPositionTx) {
- // The transaction from recents animation is in old rotation. So the position needs to
- // be rotated.
- float dx = pipTx.mPosition.x;
- float dy = pipTx.mPosition.y;
- final Matrix matrix = pipTx.getMatrix();
- if (pipTx.mRotation == 90) {
- dx = pipTx.mPosition.y;
- dy = areaBounds.right - pipTx.mPosition.x;
- matrix.postRotate(-90);
- } else if (pipTx.mRotation == -90) {
- dx = areaBounds.bottom - pipTx.mPosition.y;
- dy = pipTx.mPosition.x;
- matrix.postRotate(90);
- }
- matrix.postTranslate(dx, dy);
- final SurfaceControl leash = pinnedTask.getSurfaceControl();
- t.setMatrix(leash, matrix, new float[9]);
- if (pipTx.hasCornerRadiusSet()) {
- t.setCornerRadius(leash, pipTx.mCornerRadius);
- }
- Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
- return;
- }
-
- final PictureInPictureParams params = pinnedTask.getPictureInPictureParams();
- final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
- ? params.getSourceRectHint()
- : null;
- Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect);
- final int rotationDelta = RotationUtils.deltaRotation(oldRotation, newRotation);
- // Adjust for display cutout if applicable.
- if (sourceHintRect != null && rotationDelta == Surface.ROTATION_270) {
- if (pinnedTask.getDisplayCutoutInsets() != null) {
- final int rotationBackDelta = RotationUtils.deltaRotation(newRotation, oldRotation);
- final Rect displayCutoutInsets = RotationUtils.rotateInsets(
- Insets.of(pinnedTask.getDisplayCutoutInsets()), rotationBackDelta).toRect();
- sourceHintRect.offset(displayCutoutInsets.left, displayCutoutInsets.top);
- }
- }
- final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect)
- ? sourceHintRect : areaBounds;
- final int w = contentBounds.width();
- final int h = contentBounds.height();
- final float scale = w <= h ? (float) bounds.width() / w : (float) bounds.height() / h;
- final int insetLeft = (int) ((contentBounds.left - areaBounds.left) * scale + .5f);
- final int insetTop = (int) ((contentBounds.top - areaBounds.top) * scale + .5f);
- final Matrix matrix = new Matrix();
- matrix.setScale(scale, scale);
- matrix.postTranslate(bounds.left - insetLeft, bounds.top - insetTop);
- t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]);
- }
-
- /**
* Returns {@code true} to skip {@link Task#onConfigurationChanged} because it is expected that
* there will be a orientation change and a PiP configuration change.
*/
@@ -321,7 +233,6 @@ class PinnedTaskController {
mFreezingTaskConfig = false;
mDeferOrientationChanging = false;
mDestRotatedBounds = null;
- mPipTransaction = null;
}
/**
@@ -381,9 +292,6 @@ class PinnedTaskController {
if (mDestRotatedBounds != null) {
pw.println(prefix + " mPendingBounds=" + mDestRotatedBounds);
}
- if (mPipTransaction != null) {
- pw.println(prefix + " mPipTransaction=" + mPipTransaction);
- }
pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
pw.println(prefix + " mImeHeight=" + mImeHeight);
pw.println(prefix + " mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e864ecf60770..242aea941429 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -36,6 +36,7 @@ import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_WAKE;
+import static android.window.DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_KEEP_SCREEN_ON;
@@ -45,7 +46,6 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -79,8 +79,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
-import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
import static java.lang.Integer.MAX_VALUE;
@@ -655,9 +653,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final int count = mChildren.size();
for (int i = 0; i < count; ++i) {
final int pendingChanges = mChildren.get(i).pendingLayoutChanges;
- if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
- animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
- }
if (pendingChanges != 0) {
hasChanges = true;
}
@@ -1024,18 +1019,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return changed;
}
- boolean copyAnimToLayoutParams() {
- boolean doRequest = false;
-
- final int bulkUpdateParams = mWmService.mAnimator.mBulkUpdateParams;
- if ((bulkUpdateParams & SET_UPDATE_ROTATION) != 0) {
- mUpdateRotation = true;
- doRequest = true;
- }
-
- return doRequest;
- }
-
private final class MyHandler extends Handler {
public MyHandler(Looper looper) {
@@ -1371,7 +1354,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// When display content mode management flag is enabled, the task display area is marked as
// removed when switching from extended display to mirroring display. We need to restart the
// task display area before starting the home.
- if (enableDisplayContentModeManagement() && taskDisplayArea.shouldKeepNoTask()) {
+ if (ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()
+ && taskDisplayArea.shouldKeepNoTask()) {
taskDisplayArea.setShouldKeepNoTask(false);
}
@@ -1731,26 +1715,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
activityAssistInfos.clear();
activityAssistInfos.add(new ActivityAssistInfo(top));
// Check if the activity on the split screen.
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- top.getTask().forOtherAdjacentTasks(task -> {
- final ActivityRecord adjacentActivityRecord =
- task.getTopNonFinishingActivity();
- if (adjacentActivityRecord != null) {
- activityAssistInfos.add(
- new ActivityAssistInfo(adjacentActivityRecord));
- }
- });
- } else {
- final Task adjacentTask = top.getTask().getAdjacentTask();
- if (adjacentTask != null) {
- final ActivityRecord adjacentActivityRecord =
- adjacentTask.getTopNonFinishingActivity();
- if (adjacentActivityRecord != null) {
- activityAssistInfos.add(
- new ActivityAssistInfo(adjacentActivityRecord));
- }
+ top.getTask().forOtherAdjacentTasks(task -> {
+ final ActivityRecord adjacentActivityRecord =
+ task.getTopNonFinishingActivity();
+ if (adjacentActivityRecord != null) {
+ activityAssistInfos.add(
+ new ActivityAssistInfo(adjacentActivityRecord));
}
- }
+ });
if (rootTask == topFocusedRootTask) {
topVisibleActivities.addAll(0, activityAssistInfos);
} else {
@@ -2771,10 +2743,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return;
}
- if (enableDisplayContentModeManagement() && display.allowContentModeSwitch()) {
- mWindowManager.mDisplayWindowSettings
- .setShouldShowSystemDecorsInternalLocked(display,
- display.mDisplay.canHostTasks());
+ if (ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
+ if (display.allowContentModeSwitch()) {
+ mWindowManager.mDisplayWindowSettings
+ .setShouldShowSystemDecorsInternalLocked(display,
+ display.mDisplay.canHostTasks());
+ }
+
+ final boolean inTopology = mWindowManager.mDisplayWindowSettings
+ .shouldShowSystemDecorsLocked(display);
+ mWmService.mDisplayManagerInternal.onDisplayBelongToTopologyChanged(displayId,
+ inTopology);
}
startSystemDecorations(display, "displayAdded");
@@ -2823,7 +2802,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
displayContent.requestDisplayUpdate(
() -> {
clearDisplayInfoCaches(displayId);
- if (enableDisplayContentModeManagement()) {
+ if (ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
displayContent.onDisplayInfoChangeApplied();
}
});
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 22f0278b3a30..8587b5a9c7ca 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2461,21 +2461,6 @@ class Task extends TaskFragment {
return parentTask == null ? null : parentTask.getCreatedByOrganizerTask();
}
- /** @deprecated b/373709676 replace with {@link #forOtherAdjacentTasks(Consumer)} ()}. */
- @Deprecated
- @Nullable
- Task getAdjacentTask() {
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. "
- + "Use #forOtherAdjacentTasks instead");
- }
- final Task taskWithAdjacent = getTaskWithAdjacent();
- if (taskWithAdjacent == null) {
- return null;
- }
- return taskWithAdjacent.getAdjacentTaskFragment().asTask();
- }
-
/** Finds the first Task parent (or itself) that has adjacent. */
@Nullable
Task getTaskWithAdjacent() {
@@ -2499,11 +2484,6 @@ class Task extends TaskFragment {
* Tasks. The invoke order is not guaranteed.
*/
void forOtherAdjacentTasks(@NonNull Consumer<Task> callback) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. "
- + "Use #getAdjacentTask instead");
- }
-
final Task taskWithAdjacent = getTaskWithAdjacent();
if (taskWithAdjacent == null) {
return;
@@ -2521,10 +2501,6 @@ class Task extends TaskFragment {
* guaranteed.
*/
boolean forOtherAdjacentTasks(@NonNull Predicate<Task> callback) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. "
- + "Use getAdjacentTask instead");
- }
final Task taskWithAdjacent = getTaskWithAdjacent();
if (taskWithAdjacent == null) {
return false;
@@ -3651,20 +3627,13 @@ class Task extends TaskFragment {
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && taskFragment.isEmbedded()
&& taskFragment.hasAdjacentTaskFragment()) {
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final int[] nextLayer = { layer };
- taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
- if (adjacentTf.shouldBoostDimmer()) {
- adjacentTf.assignLayer(t, nextLayer[0]++);
- }
- });
- layer = nextLayer[0];
- } else {
- final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+ final int[] nextLayer = { layer };
+ taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
if (adjacentTf.shouldBoostDimmer()) {
- adjacentTf.assignLayer(t, layer++);
+ adjacentTf.assignLayer(t, nextLayer[0]++);
}
- }
+ });
+ layer = nextLayer[0];
}
// Place the decor surface just above the owner TaskFragment.
@@ -3862,10 +3831,11 @@ class Task extends TaskFragment {
pw.print(ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture);
pw.print(" isResizeable="); pw.println(isResizeable());
- pw.print(" isPerceptible="); pw.println(mIsPerceptible);
+ pw.print(prefix); pw.print("isPerceptible="); pw.println(mIsPerceptible);
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
- pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents);
+ pw.print(prefix); pw.print("isTrimmable=" + mIsTrimmableFromRecents);
+ pw.print(" isForceHidden="); pw.println(isForceHidden());
if (mLaunchAdjacentDisabled) {
pw.println(prefix + "mLaunchAdjacentDisabled=true");
}
@@ -4497,7 +4467,7 @@ class Task extends TaskFragment {
}
void onPictureInPictureParamsChanged() {
- if (inPinnedWindowingMode() || Flags.enableDesktopWindowingPip()) {
+ if (inPinnedWindowingMode() || DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
dispatchTaskInfoChangedIfNeeded(true /* force */);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 1966ecf57c73..fb7bab4b3e26 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -60,7 +60,6 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.LaunchParamsController.LaunchParams;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -1089,19 +1088,14 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// Use launch-adjacent-flag-root if launching with launch-adjacent flag.
if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& mLaunchAdjacentFlagRootTask != null) {
- final Task launchAdjacentRootAdjacentTask;
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final Task[] tmpTask = new Task[1];
- mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> {
- // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+.
- // Find the first adjacent for now.
- tmpTask[0] = task;
- return true;
- });
- launchAdjacentRootAdjacentTask = tmpTask[0];
- } else {
- launchAdjacentRootAdjacentTask = mLaunchAdjacentFlagRootTask.getAdjacentTask();
- }
+ final Task[] tmpTask = new Task[1];
+ mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> {
+ // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+.
+ // Find the first adjacent for now.
+ tmpTask[0] = task;
+ return true;
+ });
+ final Task launchAdjacentRootAdjacentTask = tmpTask[0];
if (sourceTask != null && (sourceTask == candidateTask
|| sourceTask.topRunningActivity() == null)) {
// Do nothing when task that is getting opened is same as the source or when
@@ -1129,14 +1123,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
if (launchRootTask == null || sourceTask == null) {
return launchRootTask;
}
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final Task adjacentRootTask = launchRootTask.getAdjacentTask();
- if (adjacentRootTask != null && (sourceTask == adjacentRootTask
- || sourceTask.isDescendantOf(adjacentRootTask))) {
- return adjacentRootTask;
- }
- return launchRootTask;
- }
final Task[] adjacentRootTask = new Task[1];
launchRootTask.forOtherAdjacentTasks(task -> {
if (sourceTask == task || sourceTask.isDescendantOf(task)) {
@@ -1163,24 +1149,16 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return sourceTask.getCreatedByOrganizerTask();
}
// Check if the candidate is already positioned in the adjacent Task.
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final Task[] adjacentRootTask = new Task[1];
- sourceTask.forOtherAdjacentTasks(task -> {
- if (candidateTask == task || candidateTask.isDescendantOf(task)) {
- adjacentRootTask[0] = task;
- return true;
- }
- return false;
- });
- if (adjacentRootTask[0] != null) {
- return adjacentRootTask[0];
- }
- } else {
- final Task adjacentTarget = taskWithAdjacent.getAdjacentTask();
- if (candidateTask == adjacentTarget
- || candidateTask.isDescendantOf(adjacentTarget)) {
- return adjacentTarget;
+ final Task[] adjacentRootTask = new Task[1];
+ sourceTask.forOtherAdjacentTasks(task -> {
+ if (candidateTask == task || candidateTask.isDescendantOf(task)) {
+ adjacentRootTask[0] = task;
+ return true;
}
+ return false;
+ });
+ if (adjacentRootTask[0] != null) {
+ return adjacentRootTask[0];
}
return sourceTask.getCreatedByOrganizerTask();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f95698a5b0bd..70dabf8d23c0 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -88,6 +87,7 @@ import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.DisplayMetrics;
@@ -234,11 +234,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
/** This task fragment will be removed when the cleanup of its children are done. */
private boolean mIsRemovalRequested;
- /** @deprecated b/373709676 replace with {@link #mAdjacentTaskFragments} */
- @Deprecated
- @Nullable
- private TaskFragment mAdjacentTaskFragment;
-
/**
* The TaskFragments that are adjacent to each other, including this TaskFragment.
* All TaskFragments in this set share the same set instance.
@@ -454,22 +449,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return service.mWindowOrganizerController.getTaskFragment(token);
}
- /** @deprecated b/373709676 replace with {@link #setAdjacentTaskFragments}. */
- @Deprecated
- void setAdjacentTaskFragment(@NonNull TaskFragment taskFragment) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- if (mAdjacentTaskFragment == taskFragment) {
- return;
- }
- resetAdjacentTaskFragment();
- mAdjacentTaskFragment = taskFragment;
- taskFragment.setAdjacentTaskFragment(this);
- return;
- }
-
- setAdjacentTaskFragments(new AdjacentSet(this, taskFragment));
- }
-
void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) {
adjacentTaskFragments.setAsAdjacent();
}
@@ -482,56 +461,18 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return mCompanionTaskFragment;
}
- /** @deprecated b/373709676 replace with {@link #clearAdjacentTaskFragments()}. */
- @Deprecated
- private void resetAdjacentTaskFragment() {
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalStateException("resetAdjacentTaskFragment shouldn't be called when"
- + " allowMultipleAdjacentTaskFragments is enabled. Use either"
- + " #clearAdjacentTaskFragments or #removeFromAdjacentTaskFragments");
- }
- // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
- if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
- mAdjacentTaskFragment.mAdjacentTaskFragment = null;
- mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
- }
- mAdjacentTaskFragment = null;
- mDelayLastActivityRemoval = false;
- }
-
void clearAdjacentTaskFragments() {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- resetAdjacentTaskFragment();
- return;
- }
-
if (mAdjacentTaskFragments != null) {
mAdjacentTaskFragments.clear();
}
}
void removeFromAdjacentTaskFragments() {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- resetAdjacentTaskFragment();
- return;
- }
-
if (mAdjacentTaskFragments != null) {
mAdjacentTaskFragments.remove(this);
}
}
- /** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */
- @Deprecated
- @Nullable
- TaskFragment getAdjacentTaskFragment() {
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. "
- + "Use #getAdjacentTaskFragments instead");
- }
- return mAdjacentTaskFragment;
- }
-
@Nullable
AdjacentSet getAdjacentTaskFragments() {
return mAdjacentTaskFragments;
@@ -560,16 +501,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
boolean hasAdjacentTaskFragment() {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- return mAdjacentTaskFragment != null;
- }
return mAdjacentTaskFragments != null;
}
boolean isAdjacentTo(@NonNull TaskFragment other) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- return mAdjacentTaskFragment == other;
- }
return other != this
&& mAdjacentTaskFragments != null
&& mAdjacentTaskFragments.contains(other);
@@ -1376,21 +1311,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (taskFragment.isAdjacentTo(this)) {
continue;
}
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds())
- || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
- return mTmpRect.intersect(adjacentTf.getBounds());
- });
- if (isOccluding) {
- return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
- }
- } else {
- final TaskFragment adjacentTaskFragment =
- taskFragment.mAdjacentTaskFragment;
- if (mTmpRect.intersect(taskFragment.getBounds())
- || mTmpRect.intersect(adjacentTaskFragment.getBounds())) {
- return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
- }
+ final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds())
+ || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+ return mTmpRect.intersect(adjacentTf.getBounds());
+ });
+ if (isOccluding) {
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
}
}
@@ -1426,37 +1352,22 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// 2. Adjacent TaskFragments do not overlap, so that if this TaskFragment is behind
// any translucent TaskFragment in the adjacent set, then this TaskFragment is
// visible behind translucent.
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments(
- adjacentTaskFragments::contains);
- if (hasTraversedAdj) {
- final boolean isTranslucent =
- isBehindTransparentTaskFragment(otherTaskFrag, starting)
- || otherTaskFrag.forOtherAdjacentTaskFragments(
- (Predicate<TaskFragment>) tf ->
- isBehindTransparentTaskFragment(tf, starting));
- if (isTranslucent) {
- // Can be visible behind a translucent adjacent TaskFragments.
- gotTranslucentFullscreen = true;
- gotTranslucentAdjacent = true;
- continue;
- }
- // Can not be visible behind adjacent TaskFragments.
- return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
- }
- } else {
- if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
- if (isBehindTransparentTaskFragment(otherTaskFrag, starting)
- || isBehindTransparentTaskFragment(
- otherTaskFrag.mAdjacentTaskFragment, starting)) {
- // Can be visible behind a translucent adjacent TaskFragments.
- gotTranslucentFullscreen = true;
- gotTranslucentAdjacent = true;
- continue;
- }
- // Can not be visible behind adjacent TaskFragments.
- return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments(
+ adjacentTaskFragments::contains);
+ if (hasTraversedAdj) {
+ final boolean isTranslucent =
+ isBehindTransparentTaskFragment(otherTaskFrag, starting)
+ || otherTaskFrag.forOtherAdjacentTaskFragments(
+ (Predicate<TaskFragment>) tf ->
+ isBehindTransparentTaskFragment(tf, starting));
+ if (isTranslucent) {
+ // Can be visible behind a translucent adjacent TaskFragments.
+ gotTranslucentFullscreen = true;
+ gotTranslucentAdjacent = true;
+ continue;
}
+ // Can not be visible behind adjacent TaskFragments.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
adjacentTaskFragments.add(otherTaskFrag);
}
@@ -1751,67 +1662,80 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
}
- try {
- final IApplicationThread appThread = next.app.getThread();
- // Deliver all pending results.
- final ArrayList<ResultInfo> a = next.results;
- if (a != null) {
- final int size = a.size();
- if (!next.finishing && size > 0) {
- if (DEBUG_RESULTS) {
- Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
- }
- final ActivityResultItem item = new ActivityResultItem(next.token, a);
- mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
+ final IApplicationThread appThread = next.app.getThread();
+ // Deliver all pending results.
+ final ArrayList<ResultInfo> a = next.results;
+ if (a != null) {
+ final int size = a.size();
+ if (!next.finishing && size > 0) {
+ if (DEBUG_RESULTS) {
+ Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
+ }
+ final ActivityResultItem item = new ActivityResultItem(next.token, a);
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
+ }
+ if (!isSuccessful) {
+ onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
+ lastFocusedRootTask);
+ return true;
}
}
+ }
- if (next.newIntents != null) {
- final NewIntentItem item =
- new NewIntentItem(next.token, next.newIntents, true /* resume */);
- mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
+ if (next.newIntents != null) {
+ final NewIntentItem item =
+ new NewIntentItem(next.token, next.newIntents, true /* resume */);
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
}
+ if (!isSuccessful) {
+ onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
+ lastFocusedRootTask);
+ return true;
+ }
+ }
- // Well the app will no longer be stopped.
- // Clear app token stopped state in window manager if needed.
- next.notifyAppResumed();
-
- EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
- next.getTask().mTaskId, next.shortComponentName);
-
- mAtmService.getAppWarningsLocked().onResumeActivity(next);
- final int topProcessState = mAtmService.mTopProcessState;
- next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
- next.abortAndClearOptionsAnimation();
- final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
- next.token, topProcessState, dc.isNextTransitionForward(),
- next.shouldSendCompatFakeFocus());
- mAtmService.getLifecycleManager().scheduleTransactionItem(
- appThread, resumeActivityItem);
-
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
- } catch (Exception e) {
- // Whoops, need to restart this activity!
- ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
- + "%s", lastState, next);
- next.setState(lastState, "resumeTopActivityInnerLocked");
+ // Well the app will no longer be stopped.
+ // Clear app token stopped state in window manager if needed.
+ next.notifyAppResumed();
- // lastResumedActivity being non-null implies there is a lastStack present.
- if (lastResumedActivity != null) {
- lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
- }
+ EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
+ next.getTask().mTaskId, next.shortComponentName);
- Slog.i(TAG, "Restarting because process died: " + next);
- if (!next.hasBeenLaunched) {
- next.hasBeenLaunched = true;
- } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
- && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
- next.showStartingWindow(false /* taskSwitch */);
- }
- mTaskSupervisor.startSpecificActivity(next, true, false);
+ mAtmService.getAppWarningsLocked().onResumeActivity(next);
+ final int topProcessState = mAtmService.mTopProcessState;
+ next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
+ next.abortAndClearOptionsAnimation();
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
+ next.token, topProcessState, dc.isNextTransitionForward(),
+ next.shouldSendCompatFakeFocus());
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, resumeActivityItem);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
+ }
+ if (!isSuccessful) {
+ onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
+ lastFocusedRootTask);
return true;
}
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
+
next.completeResumeLocked();
} else {
// Whoops, need to restart this activity!
@@ -1830,6 +1754,29 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
+ /** Likely app process has been killed. Needs to restart this activity. */
+ private void onResumeTopActivityRemoteFailure(@NonNull ActivityRecord.State lastState,
+ @NonNull ActivityRecord next, @Nullable ActivityRecord lastResumedActivity,
+ @Nullable Task lastFocusedRootTask) {
+ ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
+ + "%s", lastState, next);
+ next.setState(lastState, "resumeTopActivityInnerLocked");
+
+ // lastResumedActivity being non-null implies there is a lastStack present.
+ if (lastResumedActivity != null) {
+ lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
+ }
+
+ Slog.i(TAG, "Restarting because process died: " + next);
+ if (!next.hasBeenLaunched) {
+ next.hasBeenLaunched = true;
+ } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+ && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
+ next.showStartingWindow(false /* taskSwitch */);
+ }
+ mTaskSupervisor.startSpecificActivity(next, true, false);
+ }
+
boolean shouldSleepOrShutDownActivities() {
return shouldSleepActivities() || mAtmService.mShuttingDown;
}
@@ -2034,17 +1981,23 @@ class TaskFragment extends WindowContainer<WindowContainer> {
void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
boolean pauseImmediately, boolean autoEnteringPip, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
+ prev.mPauseSchedulePendingForPip = false;
+ EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
+ prev.shortComponentName, "userLeaving=" + userLeaving, reason);
+
+ final PauseActivityItem item = new PauseActivityItem(prev.token, prev.finishing,
+ userLeaving, pauseImmediately, autoEnteringPip);
+ boolean isSuccessful;
try {
- prev.mPauseSchedulePendingForPip = false;
- EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
- prev.shortComponentName, "userLeaving=" + userLeaving, reason);
-
- final PauseActivityItem item = new PauseActivityItem(prev.token, prev.finishing,
- userLeaving, pauseImmediately, autoEnteringPip);
- mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(), item);
- } catch (Exception e) {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ prev.app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
+ isSuccessful = false;
+ }
+ if (!isSuccessful) {
mPausingActivity = null;
mLastPausedActivity = null;
mTaskSupervisor.mNoHistoryActivities.remove(prev);
@@ -2380,15 +2333,24 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Nullable DisplayInfo mTmpOverrideDisplayInfo;
@Nullable AppCompatDisplayInsets mTmpCompatInsets;
@Nullable Rect mParentAppBoundsOverride;
+ @Nullable Rect mParentBoundsOverride;
int mTmpOverrideConfigOrientation;
boolean mUseOverrideInsetsForConfig;
void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig,
- boolean isFixedRotationTransforming) {
- mParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds());
+ boolean isFixedRotationTransforming, @Nullable Rect safeRegionBounds) {
+ mParentAppBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect(
+ parentConfig.windowConfiguration.getAppBounds());
+ mParentBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect(
+ parentConfig.windowConfiguration.getBounds());
mTmpOverrideConfigOrientation = parentConfig.orientation;
- final Insets insets;
- if (mUseOverrideInsetsForConfig && dc != null
+ Insets insets = Insets.NONE;
+ if (safeRegionBounds != null) {
+ // Modify orientation based on the parent app bounds if safe region bounds are set.
+ mTmpOverrideConfigOrientation =
+ mParentAppBoundsOverride.height() >= mParentAppBoundsOverride.width()
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ } else if (mUseOverrideInsetsForConfig && dc != null
&& !isFloating(parentConfig.windowConfiguration.getWindowingMode())) {
// Insets are decoupled from configuration by default from V+, use legacy
// compatibility behaviour for apps targeting SDK earlier than 35
@@ -2404,10 +2366,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
.getDecorInsetsInfo(rotation, dw, dh);
final Rect stableBounds = decorInsets.mOverrideConfigFrame;
mTmpOverrideConfigOrientation = stableBounds.width() > stableBounds.height()
- ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
insets = Insets.of(decorInsets.mOverrideNonDecorInsets);
- } else {
- insets = Insets.NONE;
}
mParentAppBoundsOverride.inset(insets);
}
@@ -2499,7 +2459,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) {
+ // Floating tasks shouldn't be restricted by containing app bounds.
+ if (!customContainerPolicy && !isFloating(windowingMode)) {
final Rect containingAppBounds;
if (insideParentBounds) {
containingAppBounds = useOverrideInsetsForConfig
@@ -3256,40 +3217,23 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final ArrayList<WindowContainer> siblings = getParent().mChildren;
final int zOrder = siblings.indexOf(this);
-
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- if (siblings.indexOf(getAdjacentTaskFragment()) < zOrder) {
- // early return if this TF already has higher z-ordering.
- return false;
- }
- } else {
- final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments(
- tf -> siblings.indexOf(tf) > zOrder);
- if (!hasAdjacentOnTop) {
- // early return if this TF already has higher z-ordering.
- return false;
- }
+ final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments(
+ tf -> siblings.indexOf(tf) > zOrder);
+ if (!hasAdjacentOnTop) {
+ // early return if this TF already has higher z-ordering.
+ return false;
}
final ToBooleanFunction<WindowState> getDimBehindWindow =
(w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
&& w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
|| w.mActivityRecord.isVisible());
-
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final TaskFragment adjacentTf = getAdjacentTaskFragment();
- if (adjacentTf.forAllWindows(getDimBehindWindow, true)) {
- // early return if the adjacent Tf has a dimming window.
- return false;
- }
- } else {
- final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> {
- return tf.forAllWindows(getDimBehindWindow, true);
- });
- if (adjacentHasDimmingWindow) {
- // early return if the adjacent Tf has a dimming window.
- return false;
- }
+ final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> {
+ return tf.forAllWindows(getDimBehindWindow, true);
+ });
+ if (adjacentHasDimmingWindow) {
+ // early return if the adjacent Tf has a dimming window.
+ return false;
}
// boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
@@ -3413,16 +3357,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
sb.append(" organizerProc=");
sb.append(mTaskFragmentOrganizerProcessName);
}
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- if (mAdjacentTaskFragments != null) {
- sb.append(" adjacent=");
- sb.append(mAdjacentTaskFragments);
- }
- } else {
- if (mAdjacentTaskFragment != null) {
- sb.append(" adjacent=");
- sb.append(mAdjacentTaskFragment);
- }
+ if (mAdjacentTaskFragments != null) {
+ sb.append(" adjacent=");
+ sb.append(mAdjacentTaskFragments);
}
sb.append('}');
return sb.toString();
@@ -3548,10 +3485,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be"
- + " enabled to set more than two TaskFragments adjacent to each other.");
- }
final int size = taskFragments.size();
if (size < 2) {
throw new IllegalArgumentException("Adjacent TaskFragments must contain at least"
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c78cdaa10df2..30313fc63857 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1249,7 +1249,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Skip dispatching the change for PiP task to avoid its activity drawing for the
// intermediate state which will cause flickering. The final PiP bounds in new
// rotation will be applied by PipTransition.
- ar.mDisplayContent.mPinnedTaskController.setEnterPipTransaction(null);
+ ar.mDisplayContent.mPinnedTaskController.setEnterPipWithRotatedTransientLaunch();
}
return inPip;
}
@@ -2589,9 +2589,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
// When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
// hidden unless any of them are translucent.
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- return taskFragment.getAdjacentTaskFragment().isTranslucentForTransition();
- }
return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition);
}
diff --git a/services/core/java/com/android/server/wm/ViewServer.java b/services/core/java/com/android/server/wm/ViewServer.java
index ecf5652a1e69..971e6f95d6bd 100644
--- a/services/core/java/com/android/server/wm/ViewServer.java
+++ b/services/core/java/com/android/server/wm/ViewServer.java
@@ -19,7 +19,10 @@ package com.android.server.wm;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerInternal.WindowFocusChangeListener;
+import static com.android.server.wm.WindowManagerService.WindowChangeListener;
+import android.os.IBinder;
import android.util.Slog;
import java.net.ServerSocket;
@@ -206,7 +209,7 @@ class ViewServer implements Runnable {
return result;
}
- class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {
+ class ViewServerWorker implements Runnable, WindowChangeListener, WindowFocusChangeListener {
private Socket mClient;
private boolean mNeedWindowListUpdate;
private boolean mNeedFocusedWindowUpdate;
@@ -284,7 +287,7 @@ class ViewServer implements Runnable {
}
}
- public void focusChanged() {
+ public void focusChanged(IBinder focusedWindowToken) {
synchronized(this) {
mNeedFocusedWindowUpdate = true;
notifyAll();
@@ -293,6 +296,7 @@ class ViewServer implements Runnable {
private boolean windowManagerAutolistLoop() {
mWindowManager.addWindowChangeListener(this);
+ mWindowManager.addWindowFocusChangeListener(this);
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
@@ -332,6 +336,7 @@ class ViewServer implements Runnable {
}
}
mWindowManager.removeWindowChangeListener(this);
+ mWindowManager.removeWindowFocusChangeListener(this);
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 644417ec98e5..e1553cd37d03 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -21,7 +21,6 @@ import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -58,7 +57,8 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
+import com.android.server.wallpaper.WallpaperCropper;
+import com.android.server.wallpaper.WallpaperDefaultDisplayInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -72,7 +72,6 @@ import java.util.function.Consumer;
class WallpaperController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
private WindowManagerService mService;
- private WallpaperCropUtils mWallpaperCropUtils = null;
private DisplayContent mDisplayContent;
// Larger index has higher z-order.
@@ -117,43 +116,25 @@ class WallpaperController {
private boolean mShouldOffsetWallpaperCenter;
+ // This is for WallpaperCropper, which has cropping logic for the default display only.
+ // TODO(b/400685784) make the WallpaperCropper operate on every display independently
+ private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo;
+
private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
- final boolean useShellTransition = w.mTransitionController.isShellTransitionsEnabled();
- if (!useShellTransition) {
- if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
- && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
- // If this window's app token is hidden and not animating, it is of no interest.
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
- return false;
- }
- } else {
- final ActivityRecord ar = w.mActivityRecord;
- // The animating window can still be visible on screen if it is in transition, so we
- // should check whether this window can be wallpaper target even when visibleRequested
- // is false.
- if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) {
- // An activity that is not going to remain visible shouldn't be the target.
- return false;
- }
+ final ActivityRecord ar = w.mActivityRecord;
+ // The animating window can still be visible on screen if it is in transition, so we
+ // should check whether this window can be wallpaper target even when visibleRequested
+ // is false.
+ if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) {
+ // An activity that is not going to remain visible shouldn't be the target.
+ return false;
}
if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
+ " mDrawState=" + w.mWinAnimator.mDrawState);
- final WindowContainer animatingContainer = w.mActivityRecord != null
- ? w.mActivityRecord.getAnimatingContainer() : null;
- if (!useShellTransition && animatingContainer != null
- && animatingContainer.isAnimating(TRANSITION | PARENTS)
- && AppTransition.isKeyguardGoingAwayTransitOld(animatingContainer.mTransit)
- && (animatingContainer.mTransitFlags
- & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0) {
- // Keep the wallpaper visible when Keyguard is going away.
- mFindResults.setUseTopWallpaperAsTarget(true);
- }
-
if (mService.mPolicy.isKeyguardLocked()) {
if (w.canShowWhenLocked()) {
- if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition
- ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) {
+ if (mService.mPolicy.isKeyguardOccluded() || w.inTransition()) {
// The lowest show-when-locked window decides whether to show wallpaper.
mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
|| (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
@@ -176,15 +157,11 @@ class WallpaperController {
}
}
- final boolean animationWallpaper = animatingContainer != null
- && animatingContainer.getAnimation() != null
- && animatingContainer.getAnimation().getShowWallpaper();
- final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
if (isBackNavigationTarget(w)) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found back animation wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
return true;
- } else if (hasWallpaper
+ } else if (w.hasWallpaper()
&& (w.mActivityRecord != null ? w.isOnScreen() : w.isReadyForDisplay())) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
@@ -225,12 +202,14 @@ class WallpaperController {
WallpaperController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
+ WindowManager windowManager = service.mContext.getSystemService(WindowManager.class);
Resources resources = service.mContext.getResources();
mMinWallpaperScale =
resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale);
mShouldOffsetWallpaperCenter = resources.getBoolean(
com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+ mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources);
}
void resetLargestDisplay(Display display) {
@@ -273,10 +252,6 @@ class WallpaperController {
return largestDisplaySize;
}
- void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
- mWallpaperCropUtils = wallpaperCropUtils;
- }
-
WindowState getWallpaperTarget() {
return mWallpaperTarget;
}
@@ -379,16 +354,12 @@ class WallpaperController {
int offsetY;
if (multiCrop()) {
- if (mWallpaperCropUtils == null) {
- Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting");
- return false;
- }
Point bitmapSize = new Point(
wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
SparseArray<Rect> cropHints = token.getCropHints();
wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
- : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
- wallpaperWin.isRtl());
+ : WallpaperCropper.getCrop(screenSize, mDefaultDisplayInfo, bitmapSize,
+ cropHints, wallpaperWin.isRtl());
int frameWidth = wallpaperFrame.width();
int frameHeight = wallpaperFrame.height();
float frameRatio = (float) frameWidth / frameHeight;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 80137a298ac2..3f2b40c1d7c9 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -65,9 +65,6 @@ public class WindowAnimator {
/** Time of current animation step. Reset on each iteration */
long mCurrentTime;
- int mBulkUpdateParams = 0;
- Object mLastWindowFreezeSource;
-
private boolean mInitialized = false;
private Choreographer mChoreographer;
@@ -145,7 +142,6 @@ public class WindowAnimator {
final int animationFlags = useShellTransition ? CHILDREN : (TRANSITION | CHILDREN);
boolean rootAnimating = false;
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
- mBulkUpdateParams = 0;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -202,8 +198,7 @@ public class WindowAnimator {
}
final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
- final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
- if (hasPendingLayoutChanges || doRequest) {
+ if (hasPendingLayoutChanges) {
mService.mWindowPlacerLocked.requestTraversal();
}
@@ -245,7 +240,6 @@ public class WindowAnimator {
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit"
- + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+ " hasPendingLayoutChanges=" + hasPendingLayoutChanges);
}
}
@@ -265,17 +259,6 @@ public class WindowAnimator {
mRunningExpensiveAnimations = runningExpensiveAnimations;
}
- private static String bulkUpdateParamsToString(int bulkUpdateParams) {
- StringBuilder builder = new StringBuilder(128);
- if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) {
- builder.append(" UPDATE_ROTATION");
- }
- if ((bulkUpdateParams & WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING) != 0) {
- builder.append(" SET_WALLPAPER_ACTION_PENDING");
- }
- return builder.toString();
- }
-
public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
final String subPrefix = " " + prefix;
@@ -292,11 +275,6 @@ public class WindowAnimator {
pw.print(prefix); pw.print("mCurrentTime=");
pw.println(TimeUtils.formatUptime(mCurrentTime));
}
- if (mBulkUpdateParams != 0) {
- pw.print(prefix); pw.print("mBulkUpdateParams=0x");
- pw.print(Integer.toHexString(mBulkUpdateParams));
- pw.println(bulkUpdateParamsToString(mBulkUpdateParams));
- }
}
void scheduleAnimation() {
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
deleted file mode 100644
index d3530c50348a..000000000000
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ /dev/null
@@ -1,200 +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.server.wm;
-
-import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.server.wm.AnimationSpecProto.WINDOW;
-import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.ClipRectAnimation;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
-
-import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-
-import java.io.PrintWriter;
-
-/**
- * Animation spec for changing window animations.
- */
-public class WindowChangeAnimationSpec implements AnimationSpec {
-
- private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
- private final boolean mIsAppAnimation;
- private final Rect mStartBounds;
- private final Rect mEndBounds;
- private final Rect mTmpRect = new Rect();
-
- private Animation mAnimation;
- private final boolean mIsThumbnail;
-
- static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION;
-
- public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
- float durationScale, boolean isAppAnimation, boolean isThumbnail) {
- mStartBounds = new Rect(startBounds);
- mEndBounds = new Rect(endBounds);
- mIsAppAnimation = isAppAnimation;
- mIsThumbnail = isThumbnail;
- createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
- }
-
- @Override
- public boolean getShowWallpaper() {
- return false;
- }
-
- @Override
- public long getDuration() {
- return mAnimation.getDuration();
- }
-
- /**
- * This animator behaves slightly differently depending on whether the window is growing
- * or shrinking:
- * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
- * snapshot.
- * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
- * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
- * place.
- * @param duration
- * @param displayInfo
- */
- private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
- boolean growing = mEndBounds.width() - mStartBounds.width()
- + mEndBounds.height() - mStartBounds.height() >= 0;
- float scalePart = 0.7f;
- long scalePeriod = (long) (duration * scalePart);
- float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
- + (1.f - scalePart);
- float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
- + (1.f - scalePart);
- if (mIsThumbnail) {
- AnimationSet animSet = new AnimationSet(true);
- Animation anim = new AlphaAnimation(1.f, 0.f);
- anim.setDuration(scalePeriod);
- if (!growing) {
- anim.setStartOffset(duration - scalePeriod);
- }
- animSet.addAnimation(anim);
- float endScaleX = 1.f / startScaleX;
- float endScaleY = 1.f / startScaleY;
- anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
- anim.setDuration(duration);
- animSet.addAnimation(anim);
- mAnimation = animSet;
- mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
- mEndBounds.width(), mEndBounds.height());
- } else {
- AnimationSet animSet = new AnimationSet(true);
- final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
- scaleAnim.setDuration(scalePeriod);
- if (!growing) {
- scaleAnim.setStartOffset(duration - scalePeriod);
- }
- animSet.addAnimation(scaleAnim);
- final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
- mEndBounds.left, mStartBounds.top, mEndBounds.top);
- translateAnim.setDuration(duration);
- animSet.addAnimation(translateAnim);
- Rect startClip = new Rect(mStartBounds);
- Rect endClip = new Rect(mEndBounds);
- startClip.offsetTo(0, 0);
- endClip.offsetTo(0, 0);
- final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
- clipAnim.setDuration(duration);
- animSet.addAnimation(clipAnim);
- mAnimation = animSet;
- mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
- displayInfo.appWidth, displayInfo.appHeight);
- }
- }
-
- @Override
- public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
- final TmpValues tmp = mThreadLocalTmps.get();
- if (mIsThumbnail) {
- mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
- t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
- t.setAlpha(leash, tmp.mTransformation.getAlpha());
- } else {
- mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
- final Matrix matrix = tmp.mTransformation.getMatrix();
- t.setMatrix(leash, matrix, tmp.mFloats);
-
- // The following applies an inverse scale to the clip-rect so that it crops "after" the
- // scale instead of before.
- tmp.mVecs[1] = tmp.mVecs[2] = 0;
- tmp.mVecs[0] = tmp.mVecs[3] = 1;
- matrix.mapVectors(tmp.mVecs);
- tmp.mVecs[0] = 1.f / tmp.mVecs[0];
- tmp.mVecs[3] = 1.f / tmp.mVecs[3];
- final Rect clipRect = tmp.mTransformation.getClipRect();
- mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
- mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
- mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
- mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
- t.setWindowCrop(leash, mTmpRect);
- }
- }
-
- @Override
- public long calculateStatusBarTransitionStartTime() {
- long uptime = SystemClock.uptimeMillis();
- return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
- - STATUS_BAR_TRANSITION_DURATION);
- }
-
- @Override
- public boolean canSkipFirstFrame() {
- return false;
- }
-
- @Override
- public boolean needsEarlyWakeup() {
- return mIsAppAnimation;
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.println(mAnimation.getDuration());
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(WINDOW);
- proto.write(ANIMATION, mAnimation.toString());
- proto.end(token);
- }
-
- private static class TmpValues {
- final Transformation mTransformation = new Transformation();
- final float[] mFloats = new float[9];
- final float[] mVecs = new float[4];
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e3746f18dca0..5cbba355a06f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -162,6 +162,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
protected @InsetsType int mMergedExcludeInsetsTypes = 0;
private @InsetsType int mExcludeInsetsTypes = 0;
+ /**
+ * Bounds for the safe region for this window container which control the
+ * {@link AppCompatSafeRegionPolicy}. These bounds can be passed on to the subtree if the
+ * subtree has no other bounds for the safe region. The value will be null if there are no safe
+ * region bounds for the window container.
+ */
+ @Nullable
+ private Rect mSafeRegionBounds;
+
@Nullable
private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
@@ -557,6 +566,38 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mParent != null ? mParent.mMergedExcludeInsetsTypes : 0);
}
+ /**
+ * Returns the safe region bounds on the window container. If the window container has no safe
+ * region bounds set, the safe region bounds as set on the nearest ancestor is returned.
+ */
+ @Nullable
+ Rect getSafeRegionBounds() {
+ if (mSafeRegionBounds != null) {
+ return mSafeRegionBounds;
+ }
+ if (mParent == null) {
+ return null;
+ }
+ return mParent.getSafeRegionBounds();
+ }
+
+ /**
+ * Sets the safe region bounds on the window container. Set bounds to {@code null} to reset.
+ *
+ * @param safeRegionBounds the safe region {@link Rect} that should be set on this
+ * WindowContainer
+ */
+ void setSafeRegionBounds(@Nullable Rect safeRegionBounds) {
+ if (!Flags.safeRegionLetterboxing()) {
+ Slog.i(TAG, "Feature safe region letterboxing is not available");
+ return;
+ }
+ mSafeRegionBounds = safeRegionBounds;
+ // Trigger a config change whenever this method is called since the safe region bounds
+ // can be modified (including a reset).
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ }
+
private void mergeExcludeInsetsTypesAndNotifyInsetsChanged(
@InsetsType int excludeInsetsTypesFromParent) {
final ArraySet<WindowState> changedWindows = new ArraySet<>();
@@ -2007,11 +2048,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return getActivity(r -> !r.finishing, true /* traverseTopToBottom */);
}
- ActivityRecord getTopMostVisibleFreeformActivity() {
+ ActivityRecord getTopMostFreeformActivity() {
return getActivity(r -> r.isVisibleRequested() && r.inFreeformWindowingMode(),
true /* traverseTopToBottom */);
}
+ ActivityRecord getTopMostVisibleFreeformActivity() {
+ return getActivity(r -> r.isVisible() && r.inFreeformWindowingMode(),
+ true /* traverseTopToBottom */);
+ }
+
ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
// Break down into 4 calls to avoid object creation due to capturing input params.
if (includeFinishing) {
@@ -2630,7 +2676,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (!mTransitionController.canAssignLayers(this)) return;
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
- if (Flags.useSelfSyncTransactionForLayer() && mSyncState != SYNC_STATE_NONE) {
+ if (mSyncState != SYNC_STATE_NONE) {
// When this container needs to be synced, assign layer with its own sync
// transaction to avoid out of ordering when merge.
// Still use the passed-in transaction for non-sync case, such as building finish
@@ -2647,7 +2693,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
boolean forceUpdate) {
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
if (mSurfaceControl != null && (changed || forceUpdate)) {
- if (Flags.useSelfSyncTransactionForLayer() && mSyncState != SYNC_STATE_NONE) {
+ if (mSyncState != SYNC_STATE_NONE) {
// When this container needs to be synced, assign layer with its own sync
// transaction to avoid out of ordering when merge.
// Still use the passed-in transaction for non-sync case, such as building finish
@@ -3226,6 +3272,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mLocalInsetsSources.valueAt(i).dump(childPrefix, pw);
}
}
+ pw.println(prefix + mSafeRegionBounds + " SafeRegionBounds");
}
final void updateSurfacePositionNonOrganized() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 6e224f07fcdc..5f2a2ad7f0eb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -54,7 +54,6 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import java.lang.annotation.Retention;
@@ -152,6 +151,30 @@ public abstract class WindowManagerInternal {
}
}
+ /** Interface for clients to receive callbacks related to window change. */
+ public interface WindowFocusChangeListener {
+ /**
+ * Notify on focus changed.
+ *
+ * @param focusedWindowToken the token of the newly focused window.
+ */
+ void focusChanged(@NonNull IBinder focusedWindowToken);
+ }
+
+ /**
+ * Registers a listener to be notified about window focus changes.
+ *
+ * @param listener the {@link WindowFocusChangeListener} to register.
+ */
+ public abstract void registerWindowFocusChangeListener(WindowFocusChangeListener listener);
+
+ /**
+ * Unregisters a listener that was registered via {@link #registerWindowFocusChangeListener}.
+ *
+ * @param listener the {@link WindowFocusChangeListener} to unregister.
+ */
+ public abstract void unregisterWindowFocusChangeListener(WindowFocusChangeListener listener);
+
/**
* Interface to receive a callback when the windows reported for
* accessibility changed.
@@ -748,12 +771,6 @@ public abstract class WindowManagerInternal {
public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints);
/**
- * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}.
- * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper.
- */
- public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils);
-
- /**
* Returns {@code true} if a Window owned by {@code uid} has focus.
*/
public abstract boolean isUidFocused(int uid);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 71f69048f2cb..ff43d72c5a07 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
-import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INPUT_CONSUMER;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
@@ -88,7 +87,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
@@ -147,6 +145,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSA
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
+import static com.android.server.wm.WindowManagerInternal.WindowFocusChangeListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
@@ -199,6 +198,8 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.configstore.V1_0.OptionalBool;
import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
+import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputSettings;
@@ -357,7 +358,6 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -1034,6 +1034,21 @@ public class WindowManagerService extends IWindowManager.Stub
PowerManager mPowerManager;
PowerManagerInternal mPowerManagerInternal;
+ private DeviceStateManager mDeviceStateManager;
+ private DeviceStateCallback mDeviceStateCallback;
+ private class DeviceStateCallback implements DeviceStateManager.DeviceStateCallback {
+ private DeviceState mCurrentDeviceState;
+ @Override
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
+ mCurrentDeviceState = state;
+ }
+
+ boolean isInRearDisplayOuterDefaultState() {
+ return mCurrentDeviceState != null && mCurrentDeviceState
+ .hasProperties(DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT);
+ }
+ }
+
private float mWindowAnimationScaleSetting = 1.0f;
private float mTransitionAnimationScaleSetting = 1.0f;
private float mAnimatorDurationScaleSetting = 1.0f;
@@ -1080,14 +1095,12 @@ public class WindowManagerService extends IWindowManager.Stub
private ViewServer mViewServer;
final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>();
+ final ArrayList<WindowFocusChangeListener> mWindowFocusChangeListeners = new ArrayList<>();
boolean mWindowsChanged = false;
- public interface WindowChangeListener {
+ interface WindowChangeListener {
/** Notify on windows changed */
void windowsChanged();
-
- /** Notify on focus changed */
- void focusChanged();
}
final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -1321,6 +1334,10 @@ public class WindowManagerService extends IWindowManager.Stub
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+ mDeviceStateCallback = new DeviceStateCallback();
+ mDeviceStateManager.registerCallback(new HandlerExecutor(mH), mDeviceStateCallback);
+
if (mPowerManagerInternal != null) {
mPowerManagerInternal.registerLowPowerModeObserver(
new PowerManagerInternal.LowPowerModeListener() {
@@ -2136,7 +2153,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
final DisplayContent dc = win.getDisplayContent();
- dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */);
win.resetAppOpsState();
@@ -3237,49 +3253,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- // TODO(multi-display): remove when no default display use case.
- void prepareAppTransitionNone() {
- if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
- throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
- }
- getDefaultDisplayContentLocked().prepareAppTransition(TRANSIT_NONE);
- }
-
@Override
public void overridePendingAppTransitionMultiThumbFuture(
IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
boolean scaleUp, int displayId) {
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
- Slog.w(TAG, "Attempted to call overridePendingAppTransitionMultiThumbFuture"
- + " for the display " + displayId + " that does not exist.");
- return;
- }
- displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(specsFuture,
- callback, scaleUp);
- }
+ // TODO(b/365884835): remove this method and callers.
}
@Override
public void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
int displayId) {
- if (!checkCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
- "overridePendingAppTransitionRemote()")) {
- throw new SecurityException(
- "Requires CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission");
- }
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
- Slog.w(TAG, "Attempted to call overridePendingAppTransitionRemote"
- + " for the display " + displayId + " that does not exist.");
- return;
- }
- remoteAnimationAdapter.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
- displayContent.mAppTransition.overridePendingAppTransitionRemote(
- remoteAnimationAdapter);
- }
+ // TODO(b/365884835): remove this method and callers.
}
@Override
@@ -3396,11 +3380,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean isAppTransitionStateIdle() {
- return getDefaultDisplayContentLocked().mAppTransition.isIdle();
- }
-
- @Override
public void disableKeyguard(IBinder token, String tag, int userId) {
userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false /* allowAll */, ALLOW_FULL_ONLY, "disableKeyguard", null);
@@ -5345,18 +5324,30 @@ public class WindowManagerService extends IWindowManager.Stub
return success;
}
- public void addWindowChangeListener(WindowChangeListener listener) {
+ void addWindowChangeListener(WindowChangeListener listener) {
synchronized (mGlobalLock) {
mWindowChangeListeners.add(listener);
}
}
- public void removeWindowChangeListener(WindowChangeListener listener) {
+ void removeWindowChangeListener(WindowChangeListener listener) {
synchronized (mGlobalLock) {
mWindowChangeListeners.remove(listener);
}
}
+ void addWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ synchronized (mGlobalLock) {
+ mWindowFocusChangeListeners.add(listener);
+ }
+ }
+
+ void removeWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ synchronized (mGlobalLock) {
+ mWindowFocusChangeListeners.remove(listener);
+ }
+ }
+
private void notifyWindowRemovedListeners(IBinder client) {
OnWindowRemovedListener[] windowRemovedListeners;
synchronized (mGlobalLock) {
@@ -5389,18 +5380,19 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private void notifyFocusChanged() {
- WindowChangeListener[] windowChangeListeners;
+ private void notifyFocusChanged(IBinder focusedWindowToken) {
+ WindowFocusChangeListener[] windowFocusChangeListeners;
synchronized (mGlobalLock) {
- if(mWindowChangeListeners.isEmpty()) {
+ if(mWindowFocusChangeListeners.isEmpty()) {
return;
}
- windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
- windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+ windowFocusChangeListeners =
+ new WindowFocusChangeListener[mWindowFocusChangeListeners.size()];
+ mWindowFocusChangeListeners.toArray(windowFocusChangeListeners);
}
- int N = windowChangeListeners.length;
+ int N = windowFocusChangeListeners.length;
for(int i = 0; i < N; i++) {
- windowChangeListeners[i].focusChanged();
+ windowFocusChangeListeners[i].focusChanged(focusedWindowToken);
}
}
@@ -5675,7 +5667,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) {
mAnrController.onFocusChanged(newFocusedWindow);
newFocusedWindow.reportFocusChangedSerialized(true);
- notifyFocusChanged();
+ notifyFocusChanged(newTarget.getWindowToken());
}
WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null;
@@ -7968,7 +7960,6 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void registerAppTransitionListener(AppTransitionListener listener) {
synchronized (mGlobalLock) {
- getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener);
mAtmService.getTransitionController().registerLegacyListener(listener);
}
}
@@ -8128,12 +8119,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
- mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController
- .setWallpaperCropUtils(wallpaperCropUtils);
- }
-
- @Override
public boolean isUidFocused(int uid) {
synchronized (mGlobalLock) {
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
@@ -8690,6 +8675,16 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void registerWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ WindowManagerService.this.addWindowFocusChangeListener(listener);
+ }
+
+ @Override
+ public void unregisterWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ WindowManagerService.this.removeWindowFocusChangeListener(listener);
+ }
+
+ @Override
public void registerOnWindowRemovedListener(OnWindowRemovedListener listener) {
synchronized (mGlobalLock) {
mOnWindowRemovedListeners.add(listener);
@@ -8975,6 +8970,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ if (mDeviceStateCallback.isInRearDisplayOuterDefaultState()) {
+ final Display[] rearDisplays = mDisplayManager
+ .getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR);
+ if (rearDisplays.length > 0 && rearDisplays[0].getDisplayId() == t.getDisplayId()) {
+ // Do not change display focus to the inner display if we're in this mode. Note that
+ // in this mode, the inner display is configured as a rear display.
+ Slog.w(TAG, "Ignoring focus change because device is in RDM.");
+ return;
+ }
+ }
+
clearPointerDownOutsideFocusRunnable();
final InputTarget focusedInputTarget = mFocusedInputTarget;
@@ -9392,23 +9398,6 @@ public class WindowManagerService extends IWindowManager.Stub
return focusedActivity;
}
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- final ActivityRecord adjacentTopActivity = adjacentTaskFragment.topRunningActivity();
- if (adjacentTopActivity == null) {
- // Return if no adjacent activity.
- return focusedActivity;
- }
-
- if (adjacentTopActivity.getLastWindowCreateTime()
- < focusedActivity.getLastWindowCreateTime()) {
- // Return if the current focus activity has more recently active window.
- return focusedActivity;
- }
-
- return adjacentTopActivity;
- }
-
// Find the adjacent activity with more recently active window.
final ActivityRecord[] mostRecentActiveActivity = { focusedActivity };
final long[] mostRecentActiveTime = { focusedActivity.getLastWindowCreateTime() };
@@ -9479,20 +9468,15 @@ public class WindowManagerService extends IWindowManager.Stub
// No adjacent window.
return false;
}
- final TaskFragment adjacentFragment;
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- if (fromFragment.getAdjacentTaskFragments().size() > 2) {
- throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs");
- }
- final TaskFragment[] tmpAdjacent = new TaskFragment[1];
- fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> {
- tmpAdjacent[0] = adjacentTF;
- return true;
- });
- adjacentFragment = tmpAdjacent[0];
- } else {
- adjacentFragment = fromFragment.getAdjacentTaskFragment();
+ if (fromFragment.getAdjacentTaskFragments().size() > 2) {
+ throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs");
}
+ final TaskFragment[] tmpAdjacent = new TaskFragment[1];
+ fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> {
+ tmpAdjacent[0] = adjacentTF;
+ return true;
+ });
+ final TaskFragment adjacentFragment = tmpAdjacent[0];
if (adjacentFragment.isIsolatedNav()) {
// Don't move the focus if the adjacent TF is isolated navigation.
return false;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4b4736ec6c59..c19fa8c03e0a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -78,6 +78,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
import static android.window.WindowContainerTransaction.HierarchyOp.REACHABILITY_EVENT_X;
import static android.window.WindowContainerTransaction.HierarchyOp.REACHABILITY_EVENT_Y;
@@ -473,44 +474,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
@Override
- public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
- @NonNull IWindowContainerTransactionCallback callback,
- @NonNull WindowContainerTransaction t) {
- enforceTaskPermission("startLegacyTransition()");
- final CallerInfo caller = new CallerInfo();
- final long ident = Binder.clearCallingIdentity();
- int syncId;
- try {
- synchronized (mGlobalLock) {
- if (type < 0) {
- throw new IllegalArgumentException("Can't create transition with no type");
- }
- if (mTransitionController.getTransitionPlayer() != null) {
- throw new IllegalArgumentException("Can't use legacy transitions in"
- + " when shell transitions are enabled.");
- }
- final DisplayContent dc =
- mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
- if (dc.mAppTransition.isTransitionSet()) {
- // a transition already exists, so the callback probably won't be called.
- return -1;
- }
- adapter.setCallingPidUid(caller.mPid, caller.mUid);
- dc.prepareAppTransition(type);
- dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */,
- false /* isActivityEmbedding */);
- syncId = startSyncWithOrganizer(callback);
- applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"),
- caller);
- setSyncReady(syncId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return syncId;
- }
-
- @Override
public void finishTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
enforceTaskPermission("finishTransition()");
@@ -1553,6 +1516,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
container.setExcludeInsetsTypes(hop.getExcludeInsetsTypes());
break;
}
+ case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null || !container.isAttached()) {
+ Slog.e(TAG,
+ "Attempt to operate on unknown or detached container: " + container);
+ break;
+ }
+ if (chain.mTransition != null) {
+ chain.mTransition.collect(container);
+ }
+ container.setSafeRegionBounds(hop.getSafeRegionBounds());
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
}
return effects;
}
@@ -1680,13 +1656,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
if (!taskFragment.isAdjacentTo(secondaryTaskFragment)) {
// Only have lifecycle effect if the adjacent changed.
- if (Flags.allowMultipleAdjacentTaskFragments()) {
- // Activity Embedding only set two TFs adjacent.
- taskFragment.setAdjacentTaskFragments(
- new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment));
- } else {
- taskFragment.setAdjacentTaskFragment(secondaryTaskFragment);
- }
+ // Activity Embedding only set two TFs adjacent.
+ taskFragment.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment));
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -2229,30 +2201,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
- if (!Flags.allowMultipleAdjacentTaskFragments()) {
- final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
- if (wc1 == null || !wc1.isAttached()) {
- Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
- return TRANSACT_EFFECTS_NONE;
- }
- final TaskFragment root1 = wc1.asTaskFragment();
- final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
- if (wc2 == null || !wc2.isAttached()) {
- Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
- return TRANSACT_EFFECTS_NONE;
- }
- final TaskFragment root2 = wc2.asTaskFragment();
- if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
- throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
- + " organizer root1=" + root1 + " root2=" + root2);
- }
- if (root1.isAdjacentTo(root2)) {
- return TRANSACT_EFFECTS_NONE;
- }
- root1.setAdjacentTaskFragment(root2);
- return TRANSACT_EFFECTS_LIFECYCLE;
- }
-
final IBinder[] containers = hop.getContainers();
final ArraySet<TaskFragment> adjacentRoots = new ArraySet<>();
for (IBinder container : containers) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 270de0197a4e..bdd13722aba4 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -36,8 +36,8 @@ import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STARTED;
-import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -69,7 +69,6 @@ import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Build;
-import android.os.DeadObjectException;
import android.os.FactoryTest;
import android.os.LocaleList;
import android.os.Message;
@@ -458,6 +457,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mAtm.getLifecycleManager().scheduleTransactionItemNow(
thread, configurationChangeItem);
} catch (Exception e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
+ configurationChangeItem + " owner=" + mOwner, e);
}
@@ -1793,13 +1793,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Non-UI process can handle the change directly.
mAtm.getLifecycleManager().scheduleTransactionItemNow(thread, transactionItem);
}
- } catch (DeadObjectException e) {
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// Expected if the process has been killed.
Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
+ transactionItem + " owner=" + mOwner);
- } catch (Exception e) {
- Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem="
- + transactionItem + " owner=" + mOwner, e);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
index 424b0436a008..ac675b8ba11c 100644
--- a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -72,9 +72,6 @@ final class WindowProcessControllerMap {
}
private void removeProcessFromUidMap(WindowProcessController proc) {
- if (proc == null) {
- return;
- }
final int uid = proc.mUid;
ArraySet<WindowProcessController> procSet = mUidMap.get(uid);
if (procSet != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3b7d31274326..a270af56cbcd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -100,7 +100,6 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
-import static com.android.input.flags.Flags.removeInputChannelFromWindowstate;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
@@ -170,7 +169,6 @@ import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY;
import static com.android.server.wm.WindowStateProto.IS_VISIBLE;
import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.WindowStateProto.MERGED_LOCAL_INSETS_SOURCES;
-import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.REMOVED;
import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
@@ -232,7 +230,6 @@ import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.Surface;
-import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewDebug;
@@ -400,7 +397,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* rotation.
*/
final boolean mForceSeamlesslyRotate;
- SeamlessRotator mPendingSeamlessRotate;
private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
@@ -594,13 +590,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** The time when the window was last requested to redraw for orientation change. */
private long mOrientationChangeRedrawRequestTime;
- /**
- * The orientation during the last visible call to relayout. If our
- * current orientation is different, the window can't be ready
- * to be shown.
- */
- int mLastVisibleLayoutRotation = -1;
-
/** Is this window now (or just being) removed? */
boolean mRemoved;
@@ -613,10 +602,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandleWrapper mInputWindowHandle;
- /**
- * Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled.
- */
- private InputChannel mInputChannel;
/**
* The token will be assigned to {@link InputWindowHandle#token} if this window can receive
@@ -660,15 +645,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mIsSurfacePositionPaused;
/**
- * During seamless rotation we have two phases, first the old window contents
- * are rotated to look as if they didn't move in the new coordinate system. Then we
- * have to freeze updates to this layer (to preserve the transformation) until
- * the resize actually occurs. This is true from when the transformation is set
- * and false until the transaction to resize is sent.
- */
- boolean mSeamlesslyRotated = false;
-
- /**
* Whether the IME insets have been consumed. If {@code true}, this window won't be able to
* receive visible IME insets; {@code false}, otherwise.
*/
@@ -783,11 +759,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private boolean mInsetsAnimationRunning;
- private final Consumer<SurfaceControl.Transaction> mSeamlessRotationFinishedConsumer = t -> {
- finishSeamlessRotation(t);
- updateSurfacePosition(t);
- };
-
private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
// Only apply the position to the surface when there's no leash created.
if (mSurfaceControl != null && mSurfaceControl.isValid() && !mSurfaceAnimator.hasLeash()) {
@@ -862,6 +833,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWmService.scheduleAnimationLocked();
mAnimatingTypes = animatingTypes;
+
+ if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) {
+ final InsetsStateController insetsStateController =
+ getDisplayContent().getInsetsStateController();
+ insetsStateController.onAnimatingTypesChanged(this);
+ }
}
}
@@ -898,69 +875,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return visible && mFrozenInsetsState == null;
}
- void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
- @Rotation int rotation, boolean requested) {
- // Invisible windows and the wallpaper do not participate in the seamless rotation animation
- if (!isVisibleNow() || mIsWallpaper) {
- return;
- }
-
- if (mToken.hasFixedRotationTransform()) {
- // The transform of its surface is handled by fixed rotation.
- return;
- }
- final Task task = getTask();
- if (task != null && task.inPinnedWindowingMode()) {
- // It is handled by PinnedTaskController. Note that the windowing mode of activity
- // and windows may still be fullscreen.
- return;
- }
-
- if (mPendingSeamlessRotate != null) {
- oldRotation = mPendingSeamlessRotate.getOldRotation();
- }
-
- // Skip performing seamless rotation when the controlled insets is IME with visible state.
- if (mControllableInsetProvider != null
- && mControllableInsetProvider.getSource().getType() == WindowInsets.Type.ime()) {
- return;
- }
-
- if (mForceSeamlesslyRotate || requested) {
- if (mControllableInsetProvider != null) {
- mControllableInsetProvider.startSeamlessRotation();
- }
- mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo(),
- false /* applyFixedTransformationHint */);
- // The surface position is going to be unrotated according to the last position.
- // Make sure the source position is up-to-date.
- mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
- mPendingSeamlessRotate.unrotate(transaction, this);
- getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
- true /* seamlesslyRotated */);
- applyWithNextDraw(mSeamlessRotationFinishedConsumer);
- }
- }
-
- void cancelSeamlessRotation() {
- finishSeamlessRotation(getPendingTransaction());
- }
-
- void finishSeamlessRotation(SurfaceControl.Transaction t) {
- if (mPendingSeamlessRotate == null) {
- return;
- }
-
- mPendingSeamlessRotate.finish(t, this);
- mPendingSeamlessRotate = null;
-
- getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
- false /* seamlesslyRotated */);
- if (mControllableInsetProvider != null) {
- mControllableInsetProvider.finishSeamlessRotation();
- }
- }
-
List<Rect> getSystemGestureExclusion() {
return mExclusionRects;
}
@@ -1824,12 +1738,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
- if (removeInputChannelFromWindowstate()) {
- return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
- && mInputChannelToken != null && mInputWindowHandle != null;
- }
return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
- && mInputChannel != null && mInputWindowHandle != null;
+ && mInputChannelToken != null && mInputWindowHandle != null;
}
/**
@@ -2179,8 +2089,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
&& !isDragResizing()
&& hasMovementAnimation
- && !mWinAnimator.mLastHidden
- && !mSeamlesslyRotated;
+ && !mWinAnimator.mLastHidden;
}
/**
@@ -2577,25 +2486,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mInputChannelToken != null) {
throw new IllegalStateException("Window already has an input channel token.");
}
- if (removeInputChannelFromWindowstate()) {
- String name = getName();
- InputChannel channel = mWmService.mInputManager.createInputChannel(name);
- mInputChannelToken = channel.getToken();
- mInputWindowHandle.setToken(mInputChannelToken);
- mWmService.mInputToWindowMap.put(mInputChannelToken, this);
- channel.copyTo(outInputChannel);
- channel.dispose();
- return;
- }
- if (mInputChannel != null) {
- throw new IllegalStateException("Window already has an input channel.");
- }
String name = getName();
- mInputChannel = mWmService.mInputManager.createInputChannel(name);
- mInputChannelToken = mInputChannel.getToken();
+ InputChannel channel = mWmService.mInputManager.createInputChannel(name);
+ mInputChannelToken = channel.getToken();
mInputWindowHandle.setToken(mInputChannelToken);
mWmService.mInputToWindowMap.put(mInputChannelToken, this);
- mInputChannel.copyTo(outInputChannel);
+ channel.copyTo(outInputChannel);
+ channel.dispose();
}
/**
@@ -2618,12 +2515,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mInputChannelToken = null;
}
- if (!removeInputChannelFromWindowstate()) {
- if (mInputChannel != null) {
- mInputChannel.dispose();
- mInputChannel = null;
- }
- }
mInputWindowHandle.setToken(null);
}
@@ -3880,14 +3771,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
- * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout.
+ * Returns {@code true} if activity bounds are letterboxed or letterboxed for display cutout or
+ * letterboxed for a safe region.
*
* <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
- * AppCompatLetterboxOverrides#shouldShowLetterboxUi} for more context.
+ * AppCompatLetterboxPolicy#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
return mActivityRecord != null && !isStartingWindowAssociatedToTask()
- && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
+ && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout()
+ || mActivityRecord.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
}
/** Returns {@code true} if the window is letterboxed for the display cutout. */
@@ -4016,7 +3910,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
proto.write(REMOVED, mRemoved);
proto.write(IS_ON_SCREEN, isOnScreen());
proto.write(IS_VISIBLE, isVisible);
- proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
@@ -4162,14 +4055,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
- + " seamlesslyRotate: pending=");
- if (mPendingSeamlessRotate != null) {
- mPendingSeamlessRotate.dump(pw);
- } else {
- pw.print("null");
- }
- pw.println();
if (mXOffset != 0 || mYOffset != 0) {
pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset);
@@ -4901,8 +4786,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWinAnimator.mEnterAnimationPending = true;
}
- mLastVisibleLayoutRotation = getDisplayContent().getRotation();
-
mWinAnimator.mEnteringAnimation = true;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareToDisplay");
@@ -5300,9 +5183,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
- if ((asyncRotationController != null
- && asyncRotationController.hasSeamlessOperation(mToken))
- || mPendingSeamlessRotate != null) {
+ if (asyncRotationController != null
+ && asyncRotationController.hasSeamlessOperation(mToken)) {
// Freeze position while un-rotating the window, so its surface remains at the position
// corresponding to the original rotation.
return;
@@ -5447,7 +5329,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
void assignLayer(Transaction t, int layer) {
if (mStartingData != null) {
- if (Flags.useSelfSyncTransactionForLayer() && mSyncState != SYNC_STATE_NONE) {
+ if (mSyncState != SYNC_STATE_NONE) {
// When this container needs to be synced, assign layer with its own sync
// transaction to avoid out of ordering when merge.
// Still use the passed-in transaction for non-sync case, such as building finish
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1d8d867f8dcb..0d434f51e082 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -561,7 +561,7 @@ class WindowStateAnimator {
break;
}
if (attr >= 0) {
- a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
+ a = mWin.mDisplayContent.mTransitionAnimation.loadAnimationAttr(
mWin.mAttrs, attr, TRANSIT_OLD_NONE);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index a34b5115faf9..4fb74ef00914 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -42,9 +42,6 @@ class WindowSurfacePlacer {
/** Only do a maximum of 6 repeated layouts. After that quit */
private int mLayoutRepeatCount;
- static final int SET_UPDATE_ROTATION = 1 << 0;
- static final int SET_WALLPAPER_ACTION_PENDING = 1 << 1;
-
private boolean mTraversalScheduled;
private int mDeferDepth = 0;
/** The number of layout requests when deferring. */
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 95776088aad8..adfabe1e54fd 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -169,7 +169,7 @@ cc_defaults {
"android.hardware.broadcastradio@1.1",
"android.hardware.contexthub@1.0",
"android.hardware.common.fmq-V1-ndk",
- "android.hardware.gnss-V3-cpp",
+ "android.hardware.gnss-V5-cpp",
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
@@ -193,10 +193,6 @@ cc_defaults {
"android.hardware.tv.input@1.0",
"android.hardware.tv.input-V2-ndk",
"android.hardware.vibrator-V3-ndk",
- "android.hardware.vibrator@1.0",
- "android.hardware.vibrator@1.1",
- "android.hardware.vibrator@1.2",
- "android.hardware.vibrator@1.3",
"android.hardware.vr@1.0",
"android.hidl.token@1.0-utils",
"android.frameworks.schedulerservice@1.0",
@@ -208,6 +204,7 @@ cc_defaults {
"android.system.suspend.control-V1-cpp",
"android.system.suspend.control.internal-cpp",
"android.system.suspend-V1-ndk",
+ "android_location_flags_c_lib",
"server_configurable_flags",
"service.incremental",
],
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index aeae13d83809..c674f9037aa4 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -24,12 +24,13 @@
namespace android {
static jobject nativeCreateVirtualDisplay(JNIEnv* env, jclass clazz, jstring nameObj,
- jboolean secure, jstring uniqueIdStr,
- jfloat requestedRefreshRate) {
+ jboolean secure, jboolean optimizeForPower,
+ jstring uniqueIdStr, jfloat requestedRefreshRate) {
const ScopedUtfChars name(env, nameObj);
const ScopedUtfChars uniqueId(env, uniqueIdStr);
sp<IBinder> token(SurfaceComposerClient::createVirtualDisplay(std::string(name.c_str()),
bool(secure),
+ bool(optimizeForPower),
std::string(uniqueId.c_str()),
requestedRefreshRate));
return javaObjectForIBinder(env, token);
@@ -182,7 +183,7 @@ static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong ph
static const JNINativeMethod sDisplayMethods[] = {
// clang-format off
- {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
+ {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZZLjava/lang/String;F)Landroid/os/IBinder;",
(void*)nativeCreateVirtualDisplay },
{"nativeDestroyVirtualDisplay", "(Landroid/os/IBinder;)V",
(void*)nativeDestroyVirtualDisplay },
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a8c49e11e4e9..ec8794f8073f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -671,9 +671,13 @@ void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph)
return;
}
- // TODO(b/383092013): Add topology validation
const DisplayTopologyGraph displayTopology =
android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ if (input_flags::enable_display_topology_validation() && !displayTopology.isValid()) {
+ LOG(ERROR) << "Ignoring Invalid DisplayTopology";
+ return;
+ }
+
mInputManager->getDispatcher().setDisplayTopology(displayTopology);
mInputManager->getChoreographer().setDisplayTopology(displayTopology);
}
@@ -2309,13 +2313,6 @@ static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jobject nativeImplObj, j
locationKeyCode);
}
-static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */,
- const std::shared_ptr<InputChannel>& inputChannel,
- void* data) {
- NativeInputManager* im = static_cast<NativeInputManager*>(data);
- im->removeInputChannel(inputChannel->getConnectionToken());
-}
-
static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstring nameObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2337,8 +2334,6 @@ static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstr
return nullptr;
}
- android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
- handleInputChannelDisposed, im);
return inputChannelObj;
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 9c033e25c04e..93f6e95b6d5c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -36,6 +36,7 @@
#include <android/hardware/gnss/BnGnssMeasurementCallback.h>
#include <android/hardware/gnss/BnGnssPowerIndicationCallback.h>
#include <android/hardware/gnss/BnGnssPsdsCallback.h>
+#include <android_location_flags.h>
#include <binder/IServiceManager.h>
#include <nativehelper/JNIHelp.h>
#include <pthread.h>
@@ -53,6 +54,8 @@
#include "gnss/Gnss.h"
#include "gnss/GnssAntennaInfo.h"
#include "gnss/GnssAntennaInfoCallback.h"
+#include "gnss/GnssAssistance.h"
+#include "gnss/GnssAssistanceCallback.h"
#include "gnss/GnssBatching.h"
#include "gnss/GnssConfiguration.h"
#include "gnss/GnssDebug.h"
@@ -114,6 +117,7 @@ using android::hardware::gnss::GnssConstellationType;
using android::hardware::gnss::GnssPowerStats;
using android::hardware::gnss::IGnssPowerIndication;
using android::hardware::gnss::IGnssPowerIndicationCallback;
+using android::hardware::gnss::gnss_assistance::IGnssAssistanceCallback;
using IGnssAidl = android::hardware::gnss::IGnss;
using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
@@ -140,6 +144,9 @@ std::unique_ptr<android::gnss::GnssPsdsInterface> gnssPsdsIface = nullptr;
std::unique_ptr<android::gnss::GnssVisibilityControlInterface> gnssVisibilityControlIface = nullptr;
std::unique_ptr<android::gnss::MeasurementCorrectionsInterface> gnssMeasurementCorrectionsIface =
nullptr;
+std::unique_ptr<android::gnss::GnssAssistanceInterface> gnssAssistanceIface = nullptr;
+
+namespace location_flags = android::location::flags;
namespace android {
@@ -229,6 +236,9 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc
gnss::GnssVisibilityControl_class_init_once(env, clazz);
gnss::MeasurementCorrections_class_init_once(env, clazz);
gnss::MeasurementCorrectionsCallback_class_init_once(env, clazz);
+ if (location_flags::gnss_assistance_interface_jni()) {
+ gnss::GnssAssistance_class_init_once(env, clazz);
+ }
gnss::Utils_class_init_once(env);
}
@@ -266,7 +276,9 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject
gnssBatchingIface = gnssHal->getGnssBatchingInterface();
gnssVisibilityControlIface = gnssHal->getGnssVisibilityControlInterface();
gnssPowerIndicationIface = gnssHal->getGnssPowerIndicationInterface();
-
+ if (location_flags::gnss_assistance_interface_jni()) {
+ gnssAssistanceIface = gnssHal->getGnssAssistanceInterface();
+ }
if (mCallbacksObj) {
ALOGE("Callbacks already initialized");
} else {
@@ -355,13 +367,22 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl
// Set IGnssPowerIndication.hal callback.
if (gnssPowerIndicationIface != nullptr) {
sp<IGnssPowerIndicationCallback> gnssPowerIndicationCallback =
- new GnssPowerIndicationCallback();
+ sp<GnssPowerIndicationCallback>::make();
auto status = gnssPowerIndicationIface->setCallback(gnssPowerIndicationCallback);
if (!checkAidlStatus(status, "IGnssPowerIndication setCallback() failed.")) {
gnssPowerIndicationIface = nullptr;
}
}
+ // Set IGnssAssistance callback.
+ if (gnssAssistanceIface != nullptr) {
+ sp<IGnssAssistanceCallback> gnssAssistanceCallback =
+ sp<gnss::GnssAssistanceCallback>::make();
+ if (!gnssAssistanceIface->setCallback(gnssAssistanceCallback)) {
+ ALOGI("IGnssAssistanceInterface setCallback() failed");
+ }
+ }
+
return JNI_TRUE;
}
@@ -493,6 +514,15 @@ static void android_location_gnss_hal_GnssNative_inject_psds_data(JNIEnv* env, j
gnssPsdsIface->injectPsdsData(data, length, psdsType);
}
+static void android_location_gnss_hal_GnssNative_inject_gnss_assistance(JNIEnv* env, jclass,
+ jobject gnssAssistanceObj) {
+ if (gnssAssistanceIface == nullptr) {
+ ALOGE("%s: IGnssAssistance interface not available.", __func__);
+ return;
+ }
+ gnssAssistanceIface->injectGnssAssistance(env, gnssAssistanceObj);
+}
+
static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_open(
JNIEnv* env, jobject /* obj */, jlong networkHandle, jstring apn, jint apnIpType) {
if (apn == nullptr) {
@@ -937,6 +967,8 @@ static const JNINativeMethod sLocationProviderMethods[] = {
{"native_stop_nmea_message_collection", "()Z",
reinterpret_cast<void*>(
android_location_gnss_hal_GnssNative_stop_nmea_message_collection)},
+ {"native_inject_gnss_assistance", "(Landroid/location/GnssAssistance;)V",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_gnss_assistance)},
};
static const JNINativeMethod sBatchingMethods[] = {
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 534dbb1f6cf1..11dbbdfb850e 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -19,7 +19,6 @@
#include <aidl/android/hardware/vibrator/IVibrator.h>
#include <android/binder_parcel.h>
#include <android/binder_parcel_jni.h>
-#include <android/hardware/vibrator/1.3/IVibrator.h>
#include <android/persistable_bundle_aidl.h>
#include <android_os_vibrator.h>
#include <nativehelper/JNIHelp.h>
@@ -32,8 +31,6 @@
#include "core_jni_helpers.h"
#include "jni.h"
-namespace V1_0 = android::hardware::vibrator::V1_0;
-namespace V1_3 = android::hardware::vibrator::V1_3;
namespace Aidl = aidl::android::hardware::vibrator;
using aidl::android::os::PersistableBundle;
@@ -80,31 +77,6 @@ static struct {
jfieldID timeMillis;
} sPwlePointClassInfo;
-static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
- static_cast<uint8_t>(Aidl::EffectStrength::LIGHT));
-static_assert(static_cast<uint8_t>(V1_0::EffectStrength::MEDIUM) ==
- static_cast<uint8_t>(Aidl::EffectStrength::MEDIUM));
-static_assert(static_cast<uint8_t>(V1_0::EffectStrength::STRONG) ==
- static_cast<uint8_t>(Aidl::EffectStrength::STRONG));
-
-static_assert(static_cast<uint8_t>(V1_3::Effect::CLICK) ==
- static_cast<uint8_t>(Aidl::Effect::CLICK));
-static_assert(static_cast<uint8_t>(V1_3::Effect::DOUBLE_CLICK) ==
- static_cast<uint8_t>(Aidl::Effect::DOUBLE_CLICK));
-static_assert(static_cast<uint8_t>(V1_3::Effect::TICK) == static_cast<uint8_t>(Aidl::Effect::TICK));
-static_assert(static_cast<uint8_t>(V1_3::Effect::THUD) == static_cast<uint8_t>(Aidl::Effect::THUD));
-static_assert(static_cast<uint8_t>(V1_3::Effect::POP) == static_cast<uint8_t>(Aidl::Effect::POP));
-static_assert(static_cast<uint8_t>(V1_3::Effect::HEAVY_CLICK) ==
- static_cast<uint8_t>(Aidl::Effect::HEAVY_CLICK));
-static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_1) ==
- static_cast<uint8_t>(Aidl::Effect::RINGTONE_1));
-static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_2) ==
- static_cast<uint8_t>(Aidl::Effect::RINGTONE_2));
-static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_15) ==
- static_cast<uint8_t>(Aidl::Effect::RINGTONE_15));
-static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) ==
- static_cast<uint8_t>(Aidl::Effect::TEXTURE_TICK));
-
static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
vibrator::ManagerHalController* manager =
android_server_vibrator_VibratorManagerService_getManager();
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index e72259f094bc..562e82f90bfa 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -17,7 +17,6 @@ cc_library_shared {
"-Werror",
"-Wno-unused-parameter",
"-Wthread-safety",
-
"-DEGL_EGLEXT_PROTOTYPES",
"-DGL_GLEXT_PROTOTYPES",
],
@@ -41,6 +40,8 @@ cc_library_shared {
"GnssMeasurementCallback.cpp",
"GnssNavigationMessage.cpp",
"GnssNavigationMessageCallback.cpp",
+ "GnssAssistance.cpp",
+ "GnssAssistanceCallback.cpp",
"GnssPsds.cpp",
"GnssPsdsCallback.cpp",
"GnssVisibilityControl.cpp",
@@ -61,7 +62,7 @@ cc_defaults {
"libnativehelper",
"libhardware_legacy",
"libutils",
- "android.hardware.gnss-V3-cpp",
+ "android.hardware.gnss-V5-cpp",
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
diff --git a/services/core/jni/gnss/Gnss.cpp b/services/core/jni/gnss/Gnss.cpp
index da8928b5f97f..a3fd9aa79cfb 100644
--- a/services/core/jni/gnss/Gnss.cpp
+++ b/services/core/jni/gnss/Gnss.cpp
@@ -765,4 +765,15 @@ sp<hardware::gnss::V1_0::IGnssNi> GnssHal::getGnssNiInterface() {
return nullptr;
}
+std::unique_ptr<GnssAssistanceInterface> GnssHal::getGnssAssistanceInterface() {
+ if (gnssHalAidl != nullptr) {
+ sp<hardware::gnss::gnss_assistance::IGnssAssistanceInterface> gnssAssistance;
+ auto status = gnssHalAidl->getExtensionGnssAssistanceInterface(&gnssAssistance);
+ if (checkAidlStatus(status, "Unable to get a handle to GnssAssistance")) {
+ return std::make_unique<GnssAssistanceInterface>(gnssAssistance);
+ }
+ }
+ return nullptr;
+}
+
} // namespace android::gnss
diff --git a/services/core/jni/gnss/Gnss.h b/services/core/jni/gnss/Gnss.h
index 458da8a6e514..2b6b7513a231 100644
--- a/services/core/jni/gnss/Gnss.h
+++ b/services/core/jni/gnss/Gnss.h
@@ -34,6 +34,7 @@
#include "AGnss.h"
#include "AGnssRil.h"
#include "GnssAntennaInfo.h"
+#include "GnssAssistance.h"
#include "GnssBatching.h"
#include "GnssCallback.h"
#include "GnssConfiguration.h"
@@ -115,6 +116,7 @@ public:
std::unique_ptr<GnssVisibilityControlInterface> getGnssVisibilityControlInterface();
std::unique_ptr<GnssAntennaInfoInterface> getGnssAntennaInfoInterface();
std::unique_ptr<GnssPsdsInterface> getGnssPsdsInterface();
+ std::unique_ptr<GnssAssistanceInterface> getGnssAssistanceInterface();
sp<hardware::gnss::IGnssPowerIndication> getGnssPowerIndicationInterface();
sp<hardware::gnss::V1_0::IGnssNi> getGnssNiInterface();
diff --git a/services/core/jni/gnss/GnssAssistance.cpp b/services/core/jni/gnss/GnssAssistance.cpp
new file mode 100644
index 000000000000..fff396ea126a
--- /dev/null
+++ b/services/core/jni/gnss/GnssAssistance.cpp
@@ -0,0 +1,2047 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+
+#define LOG_TAG "GnssAssistanceJni"
+
+#include "GnssAssistance.h"
+
+#include <utils/String16.h>
+
+#include "GnssAssistanceCallback.h"
+#include "Utils.h"
+
+namespace android::gnss {
+
+using GnssConstellationType = android::hardware::gnss::GnssConstellationType;
+using GnssCorrectionComponent = android::hardware::gnss::gnss_assistance::GnssCorrectionComponent;
+using GnssInterval =
+ android::hardware::gnss::gnss_assistance::GnssCorrectionComponent::GnssInterval;
+using GnssSatelliteAlmanac =
+ android::hardware::gnss::gnss_assistance::GnssAlmanac::GnssSatelliteAlmanac;
+using IonosphericCorrection = android::hardware::gnss::gnss_assistance::IonosphericCorrection;
+using PseudorangeCorrection =
+ android::hardware::gnss::gnss_assistance::GnssCorrectionComponent::PseudorangeCorrection;
+using GalileoSatelliteClockModel = android::hardware::gnss::gnss_assistance::
+ GalileoSatelliteEphemeris::GalileoSatelliteClockModel;
+using GalileoSvHealth =
+ android::hardware::gnss::gnss_assistance::GalileoSatelliteEphemeris::GalileoSvHealth;
+using GlonassSatelliteAlmanac =
+ android::hardware::gnss::gnss_assistance::GlonassAlmanac::GlonassSatelliteAlmanac;
+using GlonassSatelliteClockModel = android::hardware::gnss::gnss_assistance::
+ GlonassSatelliteEphemeris::GlonassSatelliteClockModel;
+using GlonassSatelliteOrbitModel = android::hardware::gnss::gnss_assistance::
+ GlonassSatelliteEphemeris::GlonassSatelliteOrbitModel;
+using GnssSignalType = hardware::gnss::GnssSignalType;
+using GnssConstellationType = hardware::gnss::GnssConstellationType;
+using BeidouB1CSatelliteOrbitType =
+ android::hardware::gnss::gnss_assistance::AuxiliaryInformation::BeidouB1CSatelliteOrbitType;
+using QzssSatelliteEphemeris = android::hardware::gnss::gnss_assistance::QzssSatelliteEphemeris;
+
+// Implementation of GnssAssistance (AIDL HAL)
+
+namespace {
+jmethodID method_gnssAssistanceGetGpsAssistance;
+jmethodID method_gnssAssistanceGetGlonassAssistance;
+jmethodID method_gnssAssistanceGetGalileoAssistance;
+jmethodID method_gnssAssistanceGetBeidouAssistance;
+jmethodID method_gnssAssistanceGetQzssAssistance;
+
+jmethodID method_listSize;
+jmethodID method_listGet;
+
+jmethodID method_gnssAlmanacGetIssueDateMillis;
+jmethodID method_gnssAlmanacGetIoda;
+jmethodID method_gnssAlmanacGetWeekNumber;
+jmethodID method_gnssAlmanacGetToaSeconds;
+jmethodID method_gnssAlmanacGetSatelliteAlmanacs;
+jmethodID method_gnssAlmanacIsCompleteAlmanacProvided;
+jmethodID method_satelliteAlmanacGetSvid;
+jmethodID method_satelliteAlmanacGetSvHealth;
+jmethodID method_satelliteAlmanacGetAf0;
+jmethodID method_satelliteAlmanacGetAf1;
+jmethodID method_satelliteAlmanacGetEccentricity;
+jmethodID method_satelliteAlmanacGetInclination;
+jmethodID method_satelliteAlmanacGetM0;
+jmethodID method_satelliteAlmanacGetOmega;
+jmethodID method_satelliteAlmanacGetOmega0;
+jmethodID method_satelliteAlmanacGetOmegaDot;
+jmethodID method_satelliteAlmanacGetRootA;
+
+jmethodID method_satelliteEphemerisTimeGetIode;
+jmethodID method_satelliteEphemerisTimeGetToeSeconds;
+jmethodID method_satelliteEphemerisTimeGetWeekNumber;
+
+jmethodID method_keplerianOrbitModelGetDeltaN;
+jmethodID method_keplerianOrbitModelGetEccentricity;
+jmethodID method_keplerianOrbitModelGetI0;
+jmethodID method_keplerianOrbitModelGetIDot;
+jmethodID method_keplerianOrbitModelGetM0;
+jmethodID method_keplerianOrbitModelGetOmega;
+jmethodID method_keplerianOrbitModelGetOmega0;
+jmethodID method_keplerianOrbitModelGetOmegaDot;
+jmethodID method_keplerianOrbitModelGetRootA;
+jmethodID method_keplerianOrbitModelGetSecondOrderHarmonicPerturbation;
+jmethodID method_secondOrderHarmonicPerturbationGetCic;
+jmethodID method_secondOrderHarmonicPerturbationGetCis;
+jmethodID method_secondOrderHarmonicPerturbationGetCrc;
+jmethodID method_secondOrderHarmonicPerturbationGetCrs;
+jmethodID method_secondOrderHarmonicPerturbationGetCuc;
+jmethodID method_secondOrderHarmonicPerturbationGetCus;
+
+jmethodID method_klobucharIonosphericModelGetAlpha0;
+jmethodID method_klobucharIonosphericModelGetAlpha1;
+jmethodID method_klobucharIonosphericModelGetAlpha2;
+jmethodID method_klobucharIonosphericModelGetAlpha3;
+jmethodID method_klobucharIonosphericModelGetBeta0;
+jmethodID method_klobucharIonosphericModelGetBeta1;
+jmethodID method_klobucharIonosphericModelGetBeta2;
+jmethodID method_klobucharIonosphericModelGetBeta3;
+
+jmethodID method_utcModelGetA0;
+jmethodID method_utcModelGetA1;
+jmethodID method_utcModelGetTimeOfWeek;
+jmethodID method_utcModelGetWeekNumber;
+
+jmethodID method_leapSecondsModelGetDayNumberLeapSecondsFuture;
+jmethodID method_leapSecondsModelGetLeapSeconds;
+jmethodID method_leapSecondsModelGetLeapSecondsFuture;
+jmethodID method_leapSecondsModelGetWeekNumberLeapSecondsFuture;
+
+jmethodID method_timeModelsGetTimeOfWeek;
+jmethodID method_timeModelsGetToGnss;
+jmethodID method_timeModelsGetWeekNumber;
+jmethodID method_timeModelsGetA0;
+jmethodID method_timeModelsGetA1;
+
+jmethodID method_realTimeIntegrityModelGetBadSvid;
+jmethodID method_realTimeIntegrityModelGetBadSignalTypes;
+jmethodID method_realTimeIntegrityModelGetStartDateSeconds;
+jmethodID method_realTimeIntegrityModelGetEndDateSeconds;
+jmethodID method_realTimeIntegrityModelGetPublishDateSeconds;
+jmethodID method_realTimeIntegrityModelGetAdvisoryNumber;
+jmethodID method_realTimeIntegrityModelGetAdvisoryType;
+
+jmethodID method_gnssSignalTypeGetConstellationType;
+jmethodID method_gnssSignalTypeGetCarrierFrequencyHz;
+jmethodID method_gnssSignalTypeGetCodeType;
+
+jmethodID method_auxiliaryInformationGetSvid;
+jmethodID method_auxiliaryInformationGetAvailableSignalTypes;
+jmethodID method_auxiliaryInformationGetFrequencyChannelNumber;
+jmethodID method_auxiliaryInformationGetSatType;
+
+jmethodID method_satelliteCorrectionGetSvid;
+jmethodID method_satelliteCorrectionGetIonosphericCorrections;
+jmethodID method_ionosphericCorrectionGetCarrierFrequencyHz;
+jmethodID method_ionosphericCorrectionGetIonosphericCorrection;
+jmethodID method_gnssCorrectionComponentGetPseudorangeCorrection;
+jmethodID method_gnssCorrectionComponentGetSourceKey;
+jmethodID method_gnssCorrectionComponentGetValidityInterval;
+jmethodID method_pseudorangeCorrectionGetCorrectionMeters;
+jmethodID method_pseudorangeCorrectionGetCorrectionUncertaintyMeters;
+jmethodID method_pseudorangeCorrectionGetCorrectionRateMetersPerSecond;
+jmethodID method_gnssIntervalGetStartMillisSinceGpsEpoch;
+jmethodID method_gnssIntervalGetEndMillisSinceGpsEpoch;
+
+jmethodID method_gpsAssistanceGetAlmanac;
+jmethodID method_gpsAssistanceGetIonosphericModel;
+jmethodID method_gpsAssistanceGetUtcModel;
+jmethodID method_gpsAssistanceGetLeapSecondsModel;
+jmethodID method_gpsAssistanceGetTimeModels;
+jmethodID method_gpsAssistanceGetSatelliteEphemeris;
+jmethodID method_gpsAssistanceGetRealTimeIntegrityModels;
+jmethodID method_gpsAssistanceGetSatelliteCorrections;
+jmethodID method_gpsSatelliteEphemerisGetSvid;
+jmethodID method_gpsSatelliteEphemerisGetGpsL2Params;
+jmethodID method_gpsSatelliteEphemerisGetSatelliteClockModel;
+jmethodID method_gpsSatelliteEphemerisGetSatelliteOrbitModel;
+jmethodID method_gpsSatelliteEphemerisGetSatelliteHealth;
+jmethodID method_gpsSatelliteEphemerisGetSatelliteEphemerisTime;
+jmethodID method_gpsL2ParamsGetL2Code;
+jmethodID method_gpsL2ParamsGetL2Flag;
+jmethodID method_gpsSatelliteClockModelGetAf0;
+jmethodID method_gpsSatelliteClockModelGetAf1;
+jmethodID method_gpsSatelliteClockModelGetAf2;
+jmethodID method_gpsSatelliteClockModelGetTgd;
+jmethodID method_gpsSatelliteClockModelGetIodc;
+jmethodID method_gpsSatelliteClockModelGetTimeOfClockSeconds;
+jmethodID method_gpsSatelliteHealthGetFitInt;
+jmethodID method_gpsSatelliteHealthGetSvAccur;
+jmethodID method_gpsSatelliteHealthGetSvHealth;
+
+jmethodID method_beidouAssistanceGetAlmanac;
+jmethodID method_beidouAssistanceGetIonosphericModel;
+jmethodID method_beidouAssistanceGetUtcModel;
+jmethodID method_beidouAssistanceGetLeapSecondsModel;
+jmethodID method_beidouAssistanceGetTimeModels;
+jmethodID method_beidouAssistanceGetSatelliteEphemeris;
+jmethodID method_beidouAssistanceGetSatelliteCorrections;
+jmethodID method_beidouAssistanceGetRealTimeIntegrityModels;
+jmethodID method_beidouSatelliteEphemerisGetSvid;
+jmethodID method_beidouSatelliteEphemerisGetSatelliteClockModel;
+jmethodID method_beidouSatelliteEphemerisGetSatelliteOrbitModel;
+jmethodID method_beidouSatelliteEphemerisGetSatelliteHealth;
+jmethodID method_beidouSatelliteEphemerisGetSatelliteEphemerisTime;
+jmethodID method_beidouSatelliteClockModelGetAf0;
+jmethodID method_beidouSatelliteClockModelGetAf1;
+jmethodID method_beidouSatelliteClockModelGetAf2;
+jmethodID method_beidouSatelliteClockModelGetAodc;
+jmethodID method_beidouSatelliteClockModelGetTgd1;
+jmethodID method_beidouSatelliteClockModelGetTgd2;
+jmethodID method_beidouSatelliteClockModelGetTimeOfClockSeconds;
+jmethodID method_beidouSatelliteHealthGetSatH1;
+jmethodID method_beidouSatelliteHealthGetSvAccur;
+jmethodID method_beidouSatelliteEphemerisTimeGetIode;
+jmethodID method_beidouSatelliteEphemerisTimeGetBeidouWeekNumber;
+jmethodID method_beidouSatelliteEphemerisTimeGetToeSeconds;
+
+jmethodID method_galileoAssistanceGetAlmanac;
+jmethodID method_galileoAssistanceGetIonosphericModel;
+jmethodID method_galileoAssistanceGetUtcModel;
+jmethodID method_galileoAssistanceGetLeapSecondsModel;
+jmethodID method_galileoAssistanceGetTimeModels;
+jmethodID method_galileoAssistanceGetSatelliteEphemeris;
+jmethodID method_galileoAssistanceGetSatelliteCorrections;
+jmethodID method_galileoAssistanceGetRealTimeIntegrityModels;
+jmethodID method_galileoSatelliteEphemerisGetSvid;
+jmethodID method_galileoSatelliteEphemerisGetSatelliteClockModels;
+jmethodID method_galileoSatelliteEphemerisGetSatelliteOrbitModel;
+jmethodID method_galileoSatelliteEphemerisGetSatelliteHealth;
+jmethodID method_galileoSatelliteEphemerisGetSatelliteEphemerisTime;
+jmethodID method_galileoSatelliteClockModelGetAf0;
+jmethodID method_galileoSatelliteClockModelGetAf1;
+jmethodID method_galileoSatelliteClockModelGetAf2;
+jmethodID method_galileoSatelliteClockModelGetBgdSeconds;
+jmethodID method_galileoSatelliteClockModelGetSatelliteClockType;
+jmethodID method_galileoSatelliteClockModelGetSisaMeters;
+jmethodID method_galileoSatelliteClockModelGetTimeOfClockSeconds;
+jmethodID method_galileoSvHealthGetDataValidityStatusE1b;
+jmethodID method_galileoSvHealthGetDataValidityStatusE5a;
+jmethodID method_galileoSvHealthGetDataValidityStatusE5b;
+jmethodID method_galileoSvHealthGetSignalHealthStatusE1b;
+jmethodID method_galileoSvHealthGetSignalHealthStatusE5a;
+jmethodID method_galileoSvHealthGetSignalHealthStatusE5b;
+jmethodID method_galileoIonosphericModelGetAi0;
+jmethodID method_galileoIonosphericModelGetAi1;
+jmethodID method_galileoIonosphericModelGetAi2;
+
+jmethodID method_glonassAssistanceGetAlmanac;
+jmethodID method_glonassAssistanceGetUtcModel;
+jmethodID method_glonassAssistanceGetTimeModels;
+jmethodID method_glonassAssistanceGetSatelliteEphemeris;
+jmethodID method_glonassAssistanceGetSatelliteCorrections;
+jmethodID method_glonassAlmanacGetIssueDateMillis;
+jmethodID method_glonassAlmanacGetSatelliteAlmanacs;
+jmethodID method_glonassSatelliteAlmanacGetDeltaI;
+jmethodID method_glonassSatelliteAlmanacGetDeltaT;
+jmethodID method_glonassSatelliteAlmanacGetDeltaTDot;
+jmethodID method_glonassSatelliteAlmanacGetEccentricity;
+jmethodID method_glonassSatelliteAlmanacGetFrequencyChannelNumber;
+jmethodID method_glonassSatelliteAlmanacGetLambda;
+jmethodID method_glonassSatelliteAlmanacGetOmega;
+jmethodID method_glonassSatelliteAlmanacGetSlotNumber;
+jmethodID method_glonassSatelliteAlmanacGetHealthState;
+jmethodID method_glonassSatelliteAlmanacGetTLambda;
+jmethodID method_glonassSatelliteAlmanacGetTau;
+jmethodID method_glonassSatelliteAlmanacGetIsGlonassM;
+jmethodID method_glonassSatelliteAlmanacGetCalendarDayNumber;
+jmethodID method_glonassSatelliteEphemerisGetAgeInDays;
+jmethodID method_glonassSatelliteEphemerisGetSatelliteClockModel;
+jmethodID method_glonassSatelliteEphemerisGetSatelliteOrbitModel;
+jmethodID method_glonassSatelliteEphemerisGetHealthState;
+jmethodID method_glonassSatelliteEphemerisGetSlotNumber;
+jmethodID method_glonassSatelliteEphemerisGetFrameTimeSeconds;
+jmethodID method_glonassSatelliteEphemerisGetUpdateIntervalMinutes;
+jmethodID method_glonassSatelliteEphemerisGetIsGlonassM;
+jmethodID method_glonassSatelliteEphemerisGetIsUpdateIntervalOdd;
+
+jmethodID method_glonassSatelliteOrbitModelGetX;
+jmethodID method_glonassSatelliteOrbitModelGetY;
+jmethodID method_glonassSatelliteOrbitModelGetZ;
+jmethodID method_glonassSatelliteOrbitModelGetXAccel;
+jmethodID method_glonassSatelliteOrbitModelGetYAccel;
+jmethodID method_glonassSatelliteOrbitModelGetZAccel;
+jmethodID method_glonassSatelliteOrbitModelGetXDot;
+jmethodID method_glonassSatelliteOrbitModelGetYDot;
+jmethodID method_glonassSatelliteOrbitModelGetZDot;
+jmethodID method_glonassSatelliteClockModelGetClockBias;
+jmethodID method_glonassSatelliteClockModelGetFrequencyBias;
+jmethodID method_glonassSatelliteClockModelGetFrequencyChannelNumber;
+jmethodID method_glonassSatelliteClockModelGetTimeOfClockSeconds;
+
+jmethodID method_qzssAssistanceGetAlmanac;
+jmethodID method_qzssAssistanceGetIonosphericModel;
+jmethodID method_qzssAssistanceGetUtcModel;
+jmethodID method_qzssAssistanceGetLeapSecondsModel;
+jmethodID method_qzssAssistanceGetTimeModels;
+jmethodID method_qzssAssistanceGetSatelliteEphemeris;
+jmethodID method_qzssAssistanceGetSatelliteCorrections;
+jmethodID method_qzssAssistanceGetRealTimeIntegrityModels;
+jmethodID method_qzssSatelliteEphemerisGetSvid;
+jmethodID method_qzssSatelliteEphemerisGetGpsL2Params;
+jmethodID method_qzssSatelliteEphemerisGetSatelliteClockModel;
+jmethodID method_qzssSatelliteEphemerisGetSatelliteOrbitModel;
+jmethodID method_qzssSatelliteEphemerisGetSatelliteHealth;
+jmethodID method_qzssSatelliteEphemerisGetSatelliteEphemerisTime;
+jmethodID method_qzssSatelliteClockModelGetAf0;
+jmethodID method_qzssSatelliteClockModelGetAf1;
+jmethodID method_qzssSatelliteClockModelGetAf2;
+jmethodID method_qzssSatelliteClockModelGetAodc;
+jmethodID method_qzssSatelliteClockModelGetTgd1;
+jmethodID method_qzssSatelliteClockModelGetTgd2;
+jmethodID method_qzssSatelliteClockModelGetTimeOfClockSeconds;
+} // namespace
+
+void GnssAssistance_class_init_once(JNIEnv* env, jclass clazz) {
+ // Get the methods of GnssAssistance class.
+ jclass gnssAssistanceClass = env->FindClass("android/location/GnssAssistance");
+
+ method_gnssAssistanceGetGpsAssistance =
+ env->GetMethodID(gnssAssistanceClass, "getGpsAssistance",
+ "()Landroid/location/GpsAssistance;");
+ method_gnssAssistanceGetGlonassAssistance =
+ env->GetMethodID(gnssAssistanceClass, "getGlonassAssistance",
+ "()Landroid/location/GlonassAssistance;");
+ method_gnssAssistanceGetGalileoAssistance =
+ env->GetMethodID(gnssAssistanceClass, "getGalileoAssistance",
+ "()Landroid/location/GalileoAssistance;");
+ method_gnssAssistanceGetBeidouAssistance =
+ env->GetMethodID(gnssAssistanceClass, "getBeidouAssistance",
+ "()Landroid/location/BeidouAssistance;");
+ method_gnssAssistanceGetQzssAssistance =
+ env->GetMethodID(gnssAssistanceClass, "getQzssAssistance",
+ "()Landroid/location/QzssAssistance;");
+
+ // Get the methods of List class.
+ jclass listClass = env->FindClass("java/util/List");
+
+ method_listSize = env->GetMethodID(listClass, "size", "()I");
+ method_listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
+
+ // Get the methods of GnssAlmanac class.
+ jclass gnssAlmanacClass = env->FindClass("android/location/GnssAlmanac");
+
+ method_gnssAlmanacGetIssueDateMillis =
+ env->GetMethodID(gnssAlmanacClass, "getIssueDateMillis", "()J");
+ method_gnssAlmanacGetIoda = env->GetMethodID(gnssAlmanacClass, "getIoda", "()I");
+ method_gnssAlmanacGetWeekNumber = env->GetMethodID(gnssAlmanacClass, "getWeekNumber", "()I");
+ method_gnssAlmanacGetToaSeconds = env->GetMethodID(gnssAlmanacClass, "getToaSeconds", "()I");
+ method_gnssAlmanacGetSatelliteAlmanacs =
+ env->GetMethodID(gnssAlmanacClass, "getGnssSatelliteAlmanacs", "()Ljava/util/List;");
+ method_gnssAlmanacIsCompleteAlmanacProvided =
+ env->GetMethodID(gnssAlmanacClass, "isCompleteAlmanacProvided", "()Z");
+
+ // Get the methods of SatelliteAlmanac class.
+ jclass satelliteAlmanacClass =
+ env->FindClass("android/location/GnssAlmanac$GnssSatelliteAlmanac");
+
+ method_satelliteAlmanacGetSvid = env->GetMethodID(satelliteAlmanacClass, "getSvid", "()I");
+ method_satelliteAlmanacGetSvHealth =
+ env->GetMethodID(satelliteAlmanacClass, "getSvHealth", "()I");
+ method_satelliteAlmanacGetAf0 = env->GetMethodID(satelliteAlmanacClass, "getAf0", "()D");
+ method_satelliteAlmanacGetAf1 = env->GetMethodID(satelliteAlmanacClass, "getAf1", "()D");
+ method_satelliteAlmanacGetEccentricity =
+ env->GetMethodID(satelliteAlmanacClass, "getEccentricity", "()D");
+ method_satelliteAlmanacGetInclination =
+ env->GetMethodID(satelliteAlmanacClass, "getInclination", "()D");
+ method_satelliteAlmanacGetM0 = env->GetMethodID(satelliteAlmanacClass, "getM0", "()D");
+ method_satelliteAlmanacGetOmega = env->GetMethodID(satelliteAlmanacClass, "getOmega", "()D");
+ method_satelliteAlmanacGetOmega0 = env->GetMethodID(satelliteAlmanacClass, "getOmega0", "()D");
+ method_satelliteAlmanacGetOmegaDot =
+ env->GetMethodID(satelliteAlmanacClass, "getOmegaDot", "()D");
+ method_satelliteAlmanacGetRootA = env->GetMethodID(satelliteAlmanacClass, "getRootA", "()D");
+
+ // Get the mothods of SatelliteEphemerisTime class.
+ jclass satelliteEphemerisTimeClass = env->FindClass("android/location/SatelliteEphemerisTime");
+
+ method_satelliteEphemerisTimeGetIode =
+ env->GetMethodID(satelliteEphemerisTimeClass, "getIode", "()I");
+ method_satelliteEphemerisTimeGetToeSeconds =
+ env->GetMethodID(satelliteEphemerisTimeClass, "getToeSeconds", "()I");
+ method_satelliteEphemerisTimeGetWeekNumber =
+ env->GetMethodID(satelliteEphemerisTimeClass, "getWeekNumber", "()I");
+
+ // Get the mothods of KeplerianOrbitModel class.
+ jclass keplerianOrbitModelClass = env->FindClass("android/location/KeplerianOrbitModel");
+
+ method_keplerianOrbitModelGetDeltaN =
+ env->GetMethodID(keplerianOrbitModelClass, "getDeltaN", "()D");
+ method_keplerianOrbitModelGetEccentricity =
+ env->GetMethodID(keplerianOrbitModelClass, "getEccentricity", "()D");
+ method_keplerianOrbitModelGetI0 = env->GetMethodID(keplerianOrbitModelClass, "getI0", "()D");
+ method_keplerianOrbitModelGetIDot =
+ env->GetMethodID(keplerianOrbitModelClass, "getIDot", "()D");
+ method_keplerianOrbitModelGetM0 = env->GetMethodID(keplerianOrbitModelClass, "getM0", "()D");
+ method_keplerianOrbitModelGetOmega =
+ env->GetMethodID(keplerianOrbitModelClass, "getOmega", "()D");
+ method_keplerianOrbitModelGetOmega0 =
+ env->GetMethodID(keplerianOrbitModelClass, "getOmega0", "()D");
+ method_keplerianOrbitModelGetOmegaDot =
+ env->GetMethodID(keplerianOrbitModelClass, "getOmegaDot", "()D");
+ method_keplerianOrbitModelGetRootA =
+ env->GetMethodID(keplerianOrbitModelClass, "getRootA", "()D");
+ method_keplerianOrbitModelGetSecondOrderHarmonicPerturbation =
+ env->GetMethodID(keplerianOrbitModelClass, "getSecondOrderHarmonicPerturbation",
+ "()Landroid/location/"
+ "KeplerianOrbitModel$SecondOrderHarmonicPerturbation;");
+
+ // Get the methods of SecondOrderHarmonicPerturbation class.
+ jclass secondOrderHarmonicPerturbationClass =
+ env->FindClass("android/location/KeplerianOrbitModel$SecondOrderHarmonicPerturbation");
+
+ method_secondOrderHarmonicPerturbationGetCic =
+ env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCic", "()D");
+ method_secondOrderHarmonicPerturbationGetCis =
+ env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCis", "()D");
+ method_secondOrderHarmonicPerturbationGetCrc =
+ env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCrc", "()D");
+ method_secondOrderHarmonicPerturbationGetCrs =
+ env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCrs", "()D");
+ method_secondOrderHarmonicPerturbationGetCuc =
+ env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCuc", "()D");
+ method_secondOrderHarmonicPerturbationGetCus =
+ env->GetMethodID(secondOrderHarmonicPerturbationClass, "getCus", "()D");
+
+ // Get the methods of KlobucharIonosphericModel class.
+ jclass klobucharIonosphericModelClass =
+ env->FindClass("android/location/KlobucharIonosphericModel");
+
+ method_klobucharIonosphericModelGetAlpha0 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getAlpha0", "()D");
+ method_klobucharIonosphericModelGetAlpha1 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getAlpha1", "()D");
+ method_klobucharIonosphericModelGetAlpha2 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getAlpha2", "()D");
+ method_klobucharIonosphericModelGetAlpha3 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getAlpha3", "()D");
+ method_klobucharIonosphericModelGetBeta0 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getBeta0", "()D");
+ method_klobucharIonosphericModelGetBeta1 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getBeta1", "()D");
+ method_klobucharIonosphericModelGetBeta2 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getBeta2", "()D");
+ method_klobucharIonosphericModelGetBeta3 =
+ env->GetMethodID(klobucharIonosphericModelClass, "getBeta3", "()D");
+
+ // Get the methods of UtcModel class.
+ jclass utcModelClass = env->FindClass("android/location/UtcModel");
+
+ method_utcModelGetA0 = env->GetMethodID(utcModelClass, "getA0", "()D");
+ method_utcModelGetA1 = env->GetMethodID(utcModelClass, "getA1", "()D");
+ method_utcModelGetTimeOfWeek = env->GetMethodID(utcModelClass, "getTimeOfWeek", "()I");
+ method_utcModelGetWeekNumber = env->GetMethodID(utcModelClass, "getWeekNumber", "()I");
+
+ // Get the methods of LeapSecondsModel class.
+ jclass leapSecondsModelClass = env->FindClass("android/location/LeapSecondsModel");
+
+ method_leapSecondsModelGetDayNumberLeapSecondsFuture =
+ env->GetMethodID(leapSecondsModelClass, "getDayNumberLeapSecondsFuture", "()I");
+ method_leapSecondsModelGetLeapSeconds =
+ env->GetMethodID(leapSecondsModelClass, "getLeapSeconds", "()I");
+ method_leapSecondsModelGetLeapSecondsFuture =
+ env->GetMethodID(leapSecondsModelClass, "getLeapSecondsFuture", "()I");
+ method_leapSecondsModelGetWeekNumberLeapSecondsFuture =
+ env->GetMethodID(leapSecondsModelClass, "getWeekNumberLeapSecondsFuture", "()I");
+
+ // Get the methods of TimeModel class.
+ jclass timeModelsClass = env->FindClass("android/location/TimeModel");
+
+ method_timeModelsGetTimeOfWeek = env->GetMethodID(timeModelsClass, "getTimeOfWeek", "()I");
+ method_timeModelsGetToGnss = env->GetMethodID(timeModelsClass, "getToGnss", "()I");
+ method_timeModelsGetWeekNumber = env->GetMethodID(timeModelsClass, "getWeekNumber", "()I");
+ method_timeModelsGetA0 = env->GetMethodID(timeModelsClass, "getA0", "()D");
+ method_timeModelsGetA1 = env->GetMethodID(timeModelsClass, "getA1", "()D");
+
+ // Get the methods of AuxiliaryInformation class.
+ jclass auxiliaryInformationClass = env->FindClass("android/location/AuxiliaryInformation");
+
+ method_auxiliaryInformationGetSvid =
+ env->GetMethodID(auxiliaryInformationClass, "getSvid", "()I");
+ method_auxiliaryInformationGetAvailableSignalTypes =
+ env->GetMethodID(auxiliaryInformationClass, "getAvailableSignalTypes",
+ "()Ljava/util/List;");
+ method_auxiliaryInformationGetFrequencyChannelNumber =
+ env->GetMethodID(auxiliaryInformationClass, "getFrequencyChannelNumber", "()I");
+ method_auxiliaryInformationGetSatType =
+ env->GetMethodID(auxiliaryInformationClass, "getSatType", "()I");
+
+ // Get the methods of RealTimeIntegrityModel
+ jclass realTimeIntegrityModelClass = env->FindClass("android/location/RealTimeIntegrityModel");
+
+ method_realTimeIntegrityModelGetBadSvid =
+ env->GetMethodID(realTimeIntegrityModelClass, "getBadSvid", "()I");
+ method_realTimeIntegrityModelGetBadSignalTypes =
+ env->GetMethodID(realTimeIntegrityModelClass, "getBadSignalTypes",
+ "()Ljava/util/List;");
+ method_realTimeIntegrityModelGetStartDateSeconds =
+ env->GetMethodID(realTimeIntegrityModelClass, "getStartDateSeconds", "()J");
+ method_realTimeIntegrityModelGetEndDateSeconds =
+ env->GetMethodID(realTimeIntegrityModelClass, "getEndDateSeconds", "()J");
+ method_realTimeIntegrityModelGetPublishDateSeconds =
+ env->GetMethodID(realTimeIntegrityModelClass, "getPublishDateSeconds", "()J");
+ method_realTimeIntegrityModelGetAdvisoryNumber =
+ env->GetMethodID(realTimeIntegrityModelClass, "getAdvisoryNumber",
+ "()Ljava/lang/String;");
+ method_realTimeIntegrityModelGetAdvisoryType =
+ env->GetMethodID(realTimeIntegrityModelClass, "getAdvisoryType",
+ "()Ljava/lang/String;");
+
+ // Get the methods of GnssSignalType class.
+ jclass gnssSignalTypeClass = env->FindClass("android/location/GnssSignalType");
+
+ method_gnssSignalTypeGetConstellationType =
+ env->GetMethodID(gnssSignalTypeClass, "getConstellationType", "()I");
+ method_gnssSignalTypeGetCarrierFrequencyHz =
+ env->GetMethodID(gnssSignalTypeClass, "getCarrierFrequencyHz", "()D");
+ method_gnssSignalTypeGetCodeType =
+ env->GetMethodID(gnssSignalTypeClass, "getCodeType", "()Ljava/lang/String;");
+
+ // Get the methods of SatelliteCorrection class.
+ jclass satelliteCorrectionClass =
+ env->FindClass("android/location/GnssAssistance$GnssSatelliteCorrections");
+
+ method_satelliteCorrectionGetSvid =
+ env->GetMethodID(satelliteCorrectionClass, "getSvid", "()I");
+ method_satelliteCorrectionGetIonosphericCorrections =
+ env->GetMethodID(satelliteCorrectionClass, "getIonosphericCorrections",
+ "()Ljava/util/List;");
+
+ // Get the methods of IonosphericCorrection class.
+ jclass ionosphericCorrectionClass = env->FindClass("android/location/IonosphericCorrection");
+
+ method_ionosphericCorrectionGetCarrierFrequencyHz =
+ env->GetMethodID(ionosphericCorrectionClass, "getCarrierFrequencyHz", "()J");
+ method_ionosphericCorrectionGetIonosphericCorrection =
+ env->GetMethodID(ionosphericCorrectionClass, "getIonosphericCorrection",
+ "()Landroid/location/GnssCorrectionComponent;");
+
+ // Get the methods of GnssCorrectionComponent class.
+ jclass gnssCorrectionComponentClass =
+ env->FindClass("android/location/GnssCorrectionComponent");
+
+ method_gnssCorrectionComponentGetPseudorangeCorrection =
+ env->GetMethodID(gnssCorrectionComponentClass, "getPseudorangeCorrection",
+ "()Landroid/location/GnssCorrectionComponent$PseudorangeCorrection;");
+ method_gnssCorrectionComponentGetSourceKey =
+ env->GetMethodID(gnssCorrectionComponentClass, "getSourceKey", "()Ljava/lang/String;");
+ method_gnssCorrectionComponentGetValidityInterval =
+ env->GetMethodID(gnssCorrectionComponentClass, "getValidityInterval",
+ "()Landroid/location/GnssCorrectionComponent$GnssInterval;");
+
+ // Get the methods of PseudorangeCorrection class.
+ jclass pseudorangeCorrectionClass =
+ env->FindClass("android/location/GnssCorrectionComponent$PseudorangeCorrection");
+
+ method_pseudorangeCorrectionGetCorrectionMeters =
+ env->GetMethodID(pseudorangeCorrectionClass, "getCorrectionMeters", "()D");
+ method_pseudorangeCorrectionGetCorrectionRateMetersPerSecond =
+ env->GetMethodID(pseudorangeCorrectionClass, "getCorrectionRateMetersPerSecond", "()D");
+ method_pseudorangeCorrectionGetCorrectionUncertaintyMeters =
+ env->GetMethodID(pseudorangeCorrectionClass, "getCorrectionUncertaintyMeters", "()D");
+
+ // Get the methods of GnssInterval class.
+ jclass gnssIntervalClass =
+ env->FindClass("android/location/GnssCorrectionComponent$GnssInterval");
+
+ method_gnssIntervalGetStartMillisSinceGpsEpoch =
+ env->GetMethodID(gnssIntervalClass, "getStartMillisSinceGpsEpoch", "()J");
+ method_gnssIntervalGetEndMillisSinceGpsEpoch =
+ env->GetMethodID(gnssIntervalClass, "getEndMillisSinceGpsEpoch", "()J");
+
+ // Get the methods of GpsAssistance class.
+ jclass gpsAssistanceClass = env->FindClass("android/location/GpsAssistance");
+
+ method_gpsAssistanceGetAlmanac =
+ env->GetMethodID(gpsAssistanceClass, "getAlmanac", "()Landroid/location/GnssAlmanac;");
+ method_gpsAssistanceGetIonosphericModel =
+ env->GetMethodID(gpsAssistanceClass, "getIonosphericModel",
+ "()Landroid/location/KlobucharIonosphericModel;");
+ method_gpsAssistanceGetUtcModel =
+ env->GetMethodID(gpsAssistanceClass, "getUtcModel", "()Landroid/location/UtcModel;");
+ method_gpsAssistanceGetLeapSecondsModel =
+ env->GetMethodID(gpsAssistanceClass, "getLeapSecondsModel",
+ "()Landroid/location/LeapSecondsModel;");
+ method_gpsAssistanceGetTimeModels =
+ env->GetMethodID(gpsAssistanceClass, "getTimeModels", "()Ljava/util/List;");
+ method_gpsAssistanceGetSatelliteEphemeris =
+ env->GetMethodID(gpsAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;");
+ method_gpsAssistanceGetRealTimeIntegrityModels =
+ env->GetMethodID(gpsAssistanceClass, "getRealTimeIntegrityModels",
+ "()Ljava/util/List;");
+ method_gpsAssistanceGetSatelliteCorrections =
+ env->GetMethodID(gpsAssistanceClass, "getSatelliteCorrections", "()Ljava/util/List;");
+
+ // Get the methods of GpsSatelliteEphemeris class.
+ jclass gpsSatelliteEphemerisClass = env->FindClass("android/location/GpsSatelliteEphemeris");
+
+ method_gpsSatelliteEphemerisGetSvid =
+ env->GetMethodID(gpsSatelliteEphemerisClass, "getSvid", "()I");
+ method_gpsSatelliteEphemerisGetGpsL2Params =
+ env->GetMethodID(gpsSatelliteEphemerisClass, "getGpsL2Params",
+ "()Landroid/location/GpsSatelliteEphemeris$GpsL2Params;");
+ method_gpsSatelliteEphemerisGetSatelliteClockModel =
+ env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteClockModel",
+ "()Landroid/location/GpsSatelliteEphemeris$GpsSatelliteClockModel;");
+ method_gpsSatelliteEphemerisGetSatelliteOrbitModel =
+ env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteOrbitModel",
+ "()Landroid/location/KeplerianOrbitModel;");
+ method_gpsSatelliteEphemerisGetSatelliteHealth =
+ env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteHealth",
+ "()Landroid/location/GpsSatelliteEphemeris$GpsSatelliteHealth;");
+ method_gpsSatelliteEphemerisGetSatelliteEphemerisTime =
+ env->GetMethodID(gpsSatelliteEphemerisClass, "getSatelliteEphemerisTime",
+ "()Landroid/location/SatelliteEphemerisTime;");
+
+ // Get the methods of GpsL2Params class.
+ jclass gpsL2ParamsClass = env->FindClass("android/location/GpsSatelliteEphemeris$GpsL2Params");
+ method_gpsL2ParamsGetL2Code = env->GetMethodID(gpsL2ParamsClass, "getL2Code", "()I");
+ method_gpsL2ParamsGetL2Flag = env->GetMethodID(gpsL2ParamsClass, "getL2Flag", "()I");
+
+ // Get the methods of GpsSatelliteClockModel class.
+ jclass gpsSatelliteClockModelClass =
+ env->FindClass("android/location/GpsSatelliteEphemeris$GpsSatelliteClockModel");
+ method_gpsSatelliteClockModelGetAf0 =
+ env->GetMethodID(gpsSatelliteClockModelClass, "getAf0", "()D");
+ method_gpsSatelliteClockModelGetAf1 =
+ env->GetMethodID(gpsSatelliteClockModelClass, "getAf1", "()D");
+ method_gpsSatelliteClockModelGetAf2 =
+ env->GetMethodID(gpsSatelliteClockModelClass, "getAf2", "()D");
+ method_gpsSatelliteClockModelGetTgd =
+ env->GetMethodID(gpsSatelliteClockModelClass, "getTgd", "()D");
+ method_gpsSatelliteClockModelGetIodc =
+ env->GetMethodID(gpsSatelliteClockModelClass, "getIodc", "()I");
+ method_gpsSatelliteClockModelGetTimeOfClockSeconds =
+ env->GetMethodID(gpsSatelliteClockModelClass, "getTimeOfClockSeconds", "()J");
+
+ // Get the methods of GpsSatelliteHealth class.
+ jclass gpsSatelliteHealthClass =
+ env->FindClass("android/location/GpsSatelliteEphemeris$GpsSatelliteHealth");
+ method_gpsSatelliteHealthGetFitInt =
+ env->GetMethodID(gpsSatelliteHealthClass, "getFitInt", "()D");
+ method_gpsSatelliteHealthGetSvAccur =
+ env->GetMethodID(gpsSatelliteHealthClass, "getSvAccur", "()D");
+ method_gpsSatelliteHealthGetSvHealth =
+ env->GetMethodID(gpsSatelliteHealthClass, "getSvHealth", "()I");
+
+ // Get the methods of BeidouAssistance class.
+ jclass beidouAssistanceClass = env->FindClass("android/location/BeidouAssistance");
+ method_beidouAssistanceGetAlmanac = env->GetMethodID(beidouAssistanceClass, "getAlmanac",
+ "()Landroid/location/GnssAlmanac;");
+ method_beidouAssistanceGetIonosphericModel =
+ env->GetMethodID(beidouAssistanceClass, "getIonosphericModel",
+ "()Landroid/location/KlobucharIonosphericModel;");
+ method_beidouAssistanceGetUtcModel =
+ env->GetMethodID(beidouAssistanceClass, "getUtcModel", "()Landroid/location/UtcModel;");
+ method_beidouAssistanceGetLeapSecondsModel =
+ env->GetMethodID(beidouAssistanceClass, "getLeapSecondsModel",
+ "()Landroid/location/LeapSecondsModel;");
+ method_beidouAssistanceGetTimeModels =
+ env->GetMethodID(beidouAssistanceClass, "getTimeModels", "()Ljava/util/List;");
+ method_beidouAssistanceGetSatelliteEphemeris =
+ env->GetMethodID(beidouAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;");
+ method_beidouAssistanceGetSatelliteCorrections =
+ env->GetMethodID(beidouAssistanceClass, "getSatelliteCorrections",
+ "()Ljava/util/List;");
+ method_beidouAssistanceGetRealTimeIntegrityModels =
+ env->GetMethodID(beidouAssistanceClass, "getRealTimeIntegrityModels",
+ "()Ljava/util/List;");
+
+ // Get the methods of BeidouSatelliteEphemeris class.
+ jclass beidouSatelliteEphemerisClass =
+ env->FindClass("android/location/BeidouSatelliteEphemeris");
+ method_beidouSatelliteEphemerisGetSvid =
+ env->GetMethodID(beidouSatelliteEphemerisClass, "getSvid", "()I");
+ method_beidouSatelliteEphemerisGetSatelliteClockModel =
+ env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteClockModel",
+ "()Landroid/location/"
+ "BeidouSatelliteEphemeris$BeidouSatelliteClockModel;");
+ method_beidouSatelliteEphemerisGetSatelliteOrbitModel =
+ env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteOrbitModel",
+ "()Landroid/location/KeplerianOrbitModel;");
+ method_beidouSatelliteEphemerisGetSatelliteHealth =
+ env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteHealth",
+ "()Landroid/location/BeidouSatelliteEphemeris$BeidouSatelliteHealth;");
+ method_beidouSatelliteEphemerisGetSatelliteEphemerisTime =
+ env->GetMethodID(beidouSatelliteEphemerisClass, "getSatelliteEphemerisTime",
+ "()Landroid/location/"
+ "BeidouSatelliteEphemeris$BeidouSatelliteEphemerisTime;");
+
+ // Get the methods of BeidouSatelliteClockModel
+ jclass beidouSatelliteClockModelClass =
+ env->FindClass("android/location/BeidouSatelliteEphemeris$BeidouSatelliteClockModel");
+ method_beidouSatelliteClockModelGetAf0 =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getAf0", "()D");
+ method_beidouSatelliteClockModelGetAf1 =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getAf1", "()D");
+ method_beidouSatelliteClockModelGetAf2 =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getAf2", "()D");
+ method_beidouSatelliteClockModelGetAodc =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getAodc", "()I");
+ method_beidouSatelliteClockModelGetTgd1 =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getTgd1", "()D");
+ method_beidouSatelliteClockModelGetTgd2 =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getTgd2", "()D");
+ method_beidouSatelliteClockModelGetTimeOfClockSeconds =
+ env->GetMethodID(beidouSatelliteClockModelClass, "getTimeOfClockSeconds", "()J");
+
+ // Get the methods of BeidouSatelliteHealth
+ jclass beidouSatelliteHealthClass =
+ env->FindClass("android/location/BeidouSatelliteEphemeris$BeidouSatelliteHealth");
+ method_beidouSatelliteHealthGetSatH1 =
+ env->GetMethodID(beidouSatelliteHealthClass, "getSatH1", "()I");
+ method_beidouSatelliteHealthGetSvAccur =
+ env->GetMethodID(beidouSatelliteHealthClass, "getSvAccur", "()D");
+
+ // Get the methods of BeidouSatelliteEphemerisTime
+ jclass beidouSatelliteEphemerisTimeClass = env->FindClass(
+ "android/location/BeidouSatelliteEphemeris$BeidouSatelliteEphemerisTime");
+ method_beidouSatelliteEphemerisTimeGetIode =
+ env->GetMethodID(beidouSatelliteEphemerisTimeClass, "getIode", "()I");
+ method_beidouSatelliteEphemerisTimeGetBeidouWeekNumber =
+ env->GetMethodID(beidouSatelliteEphemerisTimeClass, "getBeidouWeekNumber", "()I");
+ method_beidouSatelliteEphemerisTimeGetToeSeconds =
+ env->GetMethodID(beidouSatelliteEphemerisTimeClass, "getToeSeconds", "()I");
+
+ // Get the methods of GalileoAssistance class.
+ jclass galileoAssistanceClass = env->FindClass("android/location/GalileoAssistance");
+ method_galileoAssistanceGetAlmanac = env->GetMethodID(galileoAssistanceClass, "getAlmanac",
+ "()Landroid/location/GnssAlmanac;");
+ method_galileoAssistanceGetIonosphericModel =
+ env->GetMethodID(galileoAssistanceClass, "getIonosphericModel",
+ "()Landroid/location/KlobucharIonosphericModel;");
+ method_galileoAssistanceGetUtcModel = env->GetMethodID(galileoAssistanceClass, "getUtcModel",
+ "()Landroid/location/UtcModel;");
+ method_galileoAssistanceGetLeapSecondsModel =
+ env->GetMethodID(galileoAssistanceClass, "getLeapSecondsModel",
+ "()Landroid/location/LeapSecondsModel;");
+ method_galileoAssistanceGetTimeModels =
+ env->GetMethodID(galileoAssistanceClass, "getTimeModels", "()Ljava/util/List;");
+ method_galileoAssistanceGetSatelliteEphemeris =
+ env->GetMethodID(galileoAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;");
+ method_galileoAssistanceGetSatelliteCorrections =
+ env->GetMethodID(galileoAssistanceClass, "getSatelliteCorrections",
+ "()Ljava/util/List;");
+ method_galileoAssistanceGetRealTimeIntegrityModels =
+ env->GetMethodID(galileoAssistanceClass, "getRealTimeIntegrityModels",
+ "()Ljava/util/List;");
+
+ // Get the methods of GalileoSatelliteEphemeris class
+ jclass galileoSatelliteEphemerisClass =
+ env->FindClass("android/location/GalileoSatelliteEphemeris");
+ method_galileoSatelliteEphemerisGetSatelliteClockModels =
+ env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteClockModels",
+ "()Ljava/util/List;");
+ method_galileoSatelliteEphemerisGetSvid =
+ env->GetMethodID(galileoSatelliteEphemerisClass, "getSvid", "()I");
+ method_galileoSatelliteEphemerisGetSatelliteEphemerisTime =
+ env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteEphemerisTime",
+ "()Landroid/location/SatelliteEphemerisTime;");
+ method_galileoSatelliteEphemerisGetSatelliteHealth =
+ env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteHealth",
+ "()Landroid/location/GalileoSatelliteEphemeris$GalileoSvHealth;");
+ method_galileoSatelliteEphemerisGetSatelliteOrbitModel =
+ env->GetMethodID(galileoSatelliteEphemerisClass, "getSatelliteOrbitModel",
+ "()Landroid/location/KeplerianOrbitModel;");
+
+ // Get the methods of GalileoSatelliteClockModel class.
+ jclass galileoSatelliteClockModelClass =
+ env->FindClass("android/location/GalileoSatelliteEphemeris$GalileoSatelliteClockModel");
+ method_galileoSatelliteClockModelGetAf0 =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getAf0", "()D");
+ method_galileoSatelliteClockModelGetAf1 =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getAf1", "()D");
+ method_galileoSatelliteClockModelGetAf2 =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getAf2", "()D");
+ method_galileoSatelliteClockModelGetBgdSeconds =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getBgdSeconds", "()D");
+ method_galileoSatelliteClockModelGetSatelliteClockType =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getSatelliteClockType", "()I");
+ method_galileoSatelliteClockModelGetSisaMeters =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getSisaMeters", "()D");
+ method_galileoSatelliteClockModelGetTimeOfClockSeconds =
+ env->GetMethodID(galileoSatelliteClockModelClass, "getTimeOfClockSeconds", "()J");
+
+ // Get the methods of GalileoSvHealth class.
+ jclass galileoSvHealthClass =
+ env->FindClass("android/location/GalileoSatelliteEphemeris$GalileoSvHealth");
+ method_galileoSvHealthGetDataValidityStatusE1b =
+ env->GetMethodID(galileoSvHealthClass, "getDataValidityStatusE1b", "()I");
+ method_galileoSvHealthGetDataValidityStatusE5a =
+ env->GetMethodID(galileoSvHealthClass, "getDataValidityStatusE5a", "()I");
+ method_galileoSvHealthGetDataValidityStatusE5b =
+ env->GetMethodID(galileoSvHealthClass, "getDataValidityStatusE5b", "()I");
+ method_galileoSvHealthGetSignalHealthStatusE1b =
+ env->GetMethodID(galileoSvHealthClass, "getSignalHealthStatusE1b", "()I");
+ method_galileoSvHealthGetSignalHealthStatusE5a =
+ env->GetMethodID(galileoSvHealthClass, "getSignalHealthStatusE5a", "()I");
+ method_galileoSvHealthGetSignalHealthStatusE5b =
+ env->GetMethodID(galileoSvHealthClass, "getSignalHealthStatusE5b", "()I");
+
+ // Get the methods of GalileoIonosphericModel class.
+ jclass galileoIonosphericModelClass =
+ env->FindClass("android/location/GalileoIonosphericModel");
+ method_galileoIonosphericModelGetAi0 =
+ env->GetMethodID(galileoIonosphericModelClass, "getAi0", "()D");
+ method_galileoIonosphericModelGetAi1 =
+ env->GetMethodID(galileoIonosphericModelClass, "getAi1", "()D");
+ method_galileoIonosphericModelGetAi2 =
+ env->GetMethodID(galileoIonosphericModelClass, "getAi2", "()D");
+
+ // Get the methods of GlonassAssistance class.
+ jclass glonassAssistanceClass = env->FindClass("android/location/GlonassAssistance");
+ method_glonassAssistanceGetAlmanac = env->GetMethodID(glonassAssistanceClass, "getAlmanac",
+ "()Landroid/location/GlonassAlmanac;");
+ method_glonassAssistanceGetUtcModel = env->GetMethodID(glonassAssistanceClass, "getUtcModel",
+ "()Landroid/location/UtcModel;");
+ method_glonassAssistanceGetTimeModels =
+ env->GetMethodID(glonassAssistanceClass, "getTimeModels", "()Ljava/util/List;");
+ method_glonassAssistanceGetSatelliteEphemeris =
+ env->GetMethodID(glonassAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;");
+ method_glonassAssistanceGetSatelliteCorrections =
+ env->GetMethodID(glonassAssistanceClass, "getSatelliteCorrections",
+ "()Ljava/util/List;");
+
+ // Get the methods of GlonassAlmanac class.
+ jclass glonassAlmanacClass = env->FindClass("android/location/GlonassAlmanac");
+ method_glonassAlmanacGetIssueDateMillis =
+ env->GetMethodID(glonassAlmanacClass, "getIssueDateMillis", "()J");
+ method_glonassAlmanacGetSatelliteAlmanacs =
+ env->GetMethodID(glonassAlmanacClass, "getSatelliteAlmanacs", "()Ljava/util/List;");
+
+ // Get the methods of GlonassSatelliteAlmanac class
+ jclass glonassSatelliteAlmanacClass =
+ env->FindClass("android/location/GlonassAlmanac$GlonassSatelliteAlmanac");
+ method_glonassSatelliteAlmanacGetDeltaI =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getDeltaI", "()D");
+ method_glonassSatelliteAlmanacGetDeltaT =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getDeltaT", "()D");
+ method_glonassSatelliteAlmanacGetDeltaTDot =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getDeltaTDot", "()D");
+ method_glonassSatelliteAlmanacGetEccentricity =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getEccentricity", "()D");
+ method_glonassSatelliteAlmanacGetFrequencyChannelNumber =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getFrequencyChannelNumber", "()I");
+ method_glonassSatelliteAlmanacGetLambda =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getLambda", "()D");
+ method_glonassSatelliteAlmanacGetOmega =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getOmega", "()D");
+ method_glonassSatelliteAlmanacGetSlotNumber =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getSlotNumber", "()I");
+ method_glonassSatelliteAlmanacGetHealthState =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getHealthState", "()I");
+ method_glonassSatelliteAlmanacGetTLambda =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getTLambda", "()D");
+ method_glonassSatelliteAlmanacGetTau =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getTau", "()D");
+ method_glonassSatelliteAlmanacGetCalendarDayNumber =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "getCalendarDayNumber", "()I");
+ method_glonassSatelliteAlmanacGetIsGlonassM =
+ env->GetMethodID(glonassSatelliteAlmanacClass, "isGlonassM", "()Z");
+
+ // Get the methods of GlonassSatelliteEphemeris
+ jclass glonassSatelliteEphemerisClass =
+ env->FindClass("android/location/GlonassSatelliteEphemeris");
+ method_glonassSatelliteEphemerisGetAgeInDays =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getAgeInDays", "()I");
+ method_glonassSatelliteEphemerisGetFrameTimeSeconds =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getFrameTimeSeconds", "()D");
+ method_glonassSatelliteEphemerisGetHealthState =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getHealthState", "()I");
+ method_glonassSatelliteEphemerisGetSlotNumber =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getSlotNumber", "()I");
+ method_glonassSatelliteEphemerisGetSatelliteClockModel =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getSatelliteClockModel",
+ "()Landroid/location/"
+ "GlonassSatelliteEphemeris$GlonassSatelliteClockModel;");
+ method_glonassSatelliteEphemerisGetSatelliteOrbitModel =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getSatelliteOrbitModel",
+ "()Landroid/location/"
+ "GlonassSatelliteEphemeris$GlonassSatelliteOrbitModel;");
+ method_glonassSatelliteEphemerisGetUpdateIntervalMinutes =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "getUpdateIntervalMinutes", "()I");
+ method_glonassSatelliteEphemerisGetIsGlonassM =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "isGlonassM", "()Z");
+ method_glonassSatelliteEphemerisGetIsUpdateIntervalOdd =
+ env->GetMethodID(glonassSatelliteEphemerisClass, "isUpdateIntervalOdd", "()Z");
+
+ // Get the methods of GlonassSatelliteOrbitModel
+ jclass glonassSatelliteOrbitModelClass =
+ env->FindClass("android/location/GlonassSatelliteEphemeris$GlonassSatelliteOrbitModel");
+ method_glonassSatelliteOrbitModelGetX =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getX", "()D");
+ method_glonassSatelliteOrbitModelGetXAccel =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getXAccel", "()D");
+ method_glonassSatelliteOrbitModelGetXDot =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getXDot", "()D");
+ method_glonassSatelliteOrbitModelGetY =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getY", "()D");
+ method_glonassSatelliteOrbitModelGetYAccel =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getYAccel", "()D");
+ method_glonassSatelliteOrbitModelGetYDot =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getYDot", "()D");
+ method_glonassSatelliteOrbitModelGetZ =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getZ", "()D");
+ method_glonassSatelliteOrbitModelGetZAccel =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getZAccel", "()D");
+ method_glonassSatelliteOrbitModelGetZDot =
+ env->GetMethodID(glonassSatelliteOrbitModelClass, "getZDot", "()D");
+
+ // Get the methods of GlonassSatelliteClockModel
+ jclass glonassSatelliteClockModelClass =
+ env->FindClass("android/location/GlonassSatelliteEphemeris$GlonassSatelliteClockModel");
+ method_glonassSatelliteClockModelGetClockBias =
+ env->GetMethodID(glonassSatelliteClockModelClass, "getClockBias", "()D");
+ method_glonassSatelliteClockModelGetFrequencyBias =
+ env->GetMethodID(glonassSatelliteClockModelClass, "getFrequencyBias", "()D");
+ method_glonassSatelliteClockModelGetFrequencyChannelNumber =
+ env->GetMethodID(glonassSatelliteClockModelClass, "getFrequencyChannelNumber", "()I");
+ method_glonassSatelliteClockModelGetTimeOfClockSeconds =
+ env->GetMethodID(glonassSatelliteClockModelClass, "getTimeOfClockSeconds", "()J");
+
+ // Get the methods of QzssAssistance class.
+ jclass qzssAssistanceClass = env->FindClass("android/location/QzssAssistance");
+ method_qzssAssistanceGetAlmanac =
+ env->GetMethodID(qzssAssistanceClass, "getAlmanac", "()Landroid/location/GnssAlmanac;");
+ method_qzssAssistanceGetIonosphericModel =
+ env->GetMethodID(qzssAssistanceClass, "getIonosphericModel",
+ "()Landroid/location/KlobucharIonosphericModel;");
+ method_qzssAssistanceGetUtcModel =
+ env->GetMethodID(qzssAssistanceClass, "getUtcModel", "()Landroid/location/UtcModel;");
+ method_qzssAssistanceGetLeapSecondsModel =
+ env->GetMethodID(qzssAssistanceClass, "getLeapSecondsModel",
+ "()Landroid/location/LeapSecondsModel;");
+ method_qzssAssistanceGetTimeModels =
+ env->GetMethodID(qzssAssistanceClass, "getTimeModels", "()Ljava/util/List;");
+ method_qzssAssistanceGetSatelliteEphemeris =
+ env->GetMethodID(qzssAssistanceClass, "getSatelliteEphemeris", "()Ljava/util/List;");
+ method_qzssAssistanceGetSatelliteCorrections =
+ env->GetMethodID(qzssAssistanceClass, "getSatelliteCorrections", "()Ljava/util/List;");
+
+ // Get the methods of QzssSatelliteEphemeris class.
+ jclass qzssSatelliteEphemerisClass = env->FindClass("android/location/QzssSatelliteEphemeris");
+ method_qzssSatelliteEphemerisGetSvid =
+ env->GetMethodID(qzssSatelliteEphemerisClass, "getSvid", "()I");
+ method_qzssSatelliteEphemerisGetGpsL2Params =
+ env->GetMethodID(qzssSatelliteEphemerisClass, "getGpsL2Params",
+ "()Landroid/location/GpsSatelliteEphemeris$GpsL2Params;");
+ method_qzssSatelliteEphemerisGetSatelliteEphemerisTime =
+ env->GetMethodID(qzssSatelliteEphemerisClass, "getSatelliteEphemerisTime",
+ "()Landroid/location/SatelliteEphemerisTime;");
+ method_qzssSatelliteEphemerisGetSatelliteHealth =
+ env->GetMethodID(qzssSatelliteEphemerisClass, "getSatelliteHealth",
+ "()Landroid/location/GpsSatelliteEphemeris$GpsSatelliteHealth;");
+ method_qzssSatelliteEphemerisGetSatelliteOrbitModel =
+ env->GetMethodID(qzssSatelliteEphemerisClass, "getSatelliteOrbitModel",
+ "()Landroid/location/KeplerianOrbitModel;");
+}
+
+GnssAssistanceInterface::GnssAssistanceInterface(
+ const sp<IGnssAssistanceInterface>& iGnssAssistance)
+ : mGnssAssistanceInterface(iGnssAssistance) {
+ assert(mGnssAssistanceInterface != nullptr);
+}
+
+jboolean GnssAssistanceInterface::injectGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj) {
+ GnssAssistance gnssAssistance;
+ GnssAssistanceUtil::setGnssAssistance(env, gnssAssistanceObj, gnssAssistance);
+ auto status = mGnssAssistanceInterface->injectGnssAssistance(gnssAssistance);
+ return checkAidlStatus(status, "IGnssAssistanceInterface injectGnssAssistance() failed.");
+}
+
+jboolean GnssAssistanceInterface::setCallback(const sp<IGnssAssistanceCallback>& callback) {
+ auto status = mGnssAssistanceInterface->setCallback(callback);
+ return checkAidlStatus(status, "IGnssAssistanceInterface setCallback() failed.");
+}
+
+void GnssAssistanceUtil::setGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj,
+ GnssAssistance& gnssAssistance) {
+ jobject gpsAssistanceObj =
+ env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetGpsAssistance);
+ jobject glonassAssistanceObj =
+ env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetGlonassAssistance);
+ jobject qzssAssistanceObj =
+ env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetQzssAssistance);
+ jobject galileoAssistanceObj =
+ env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetGalileoAssistance);
+ jobject beidouAssistanceObj =
+ env->CallObjectMethod(gnssAssistanceObj, method_gnssAssistanceGetBeidouAssistance);
+ GnssAssistanceUtil::setGpsAssistance(env, gpsAssistanceObj, gnssAssistance.gpsAssistance);
+ GnssAssistanceUtil::setGlonassAssistance(env, glonassAssistanceObj,
+ gnssAssistance.glonassAssistance);
+ GnssAssistanceUtil::setQzssAssistance(env, qzssAssistanceObj, gnssAssistance.qzssAssistance);
+ GnssAssistanceUtil::setGalileoAssistance(env, galileoAssistanceObj,
+ gnssAssistance.galileoAssistance);
+ GnssAssistanceUtil::setBeidouAssistance(env, beidouAssistanceObj,
+ gnssAssistance.beidouAssistance);
+ env->DeleteLocalRef(gpsAssistanceObj);
+ env->DeleteLocalRef(glonassAssistanceObj);
+ env->DeleteLocalRef(qzssAssistanceObj);
+ env->DeleteLocalRef(galileoAssistanceObj);
+ env->DeleteLocalRef(beidouAssistanceObj);
+}
+
+void GnssAssistanceUtil::setQzssAssistance(JNIEnv* env, jobject qzssAssistanceObj,
+ QzssAssistance& qzssAssistance) {
+ jobject qzssAlmanacObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetAlmanac);
+ jobject qzssIonosphericModelObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetIonosphericModel);
+ jobject qzssUtcModelObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetUtcModel);
+ jobject qzssLeapSecondsModelObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetLeapSecondsModel);
+ jobject qzssTimeModelsObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetTimeModels);
+ jobject qzssSatelliteEphemerisObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetSatelliteEphemeris);
+ jobject qzssSatelliteCorrectionsObj =
+ env->CallObjectMethod(qzssAssistanceObj, method_qzssAssistanceGetSatelliteCorrections);
+ setGnssAlmanac(env, qzssAlmanacObj, qzssAssistance.almanac);
+ setKlobucharIonosphericModel(env, qzssIonosphericModelObj, qzssAssistance.ionosphericModel);
+ setUtcModel(env, qzssUtcModelObj, qzssAssistance.utcModel);
+ setLeapSecondsModel(env, qzssLeapSecondsModelObj, qzssAssistance.leapSecondsModel);
+ setTimeModels(env, qzssTimeModelsObj, qzssAssistance.timeModels);
+ setGpsOrQzssSatelliteEphemeris<QzssSatelliteEphemeris>(env, qzssSatelliteEphemerisObj,
+ qzssAssistance.satelliteEphemeris);
+ setSatelliteCorrections(env, qzssSatelliteCorrectionsObj, qzssAssistance.satelliteCorrections);
+ env->DeleteLocalRef(qzssAlmanacObj);
+ env->DeleteLocalRef(qzssIonosphericModelObj);
+ env->DeleteLocalRef(qzssUtcModelObj);
+ env->DeleteLocalRef(qzssLeapSecondsModelObj);
+ env->DeleteLocalRef(qzssTimeModelsObj);
+ env->DeleteLocalRef(qzssSatelliteEphemerisObj);
+ env->DeleteLocalRef(qzssSatelliteCorrectionsObj);
+}
+
+void GnssAssistanceUtil::setGlonassAssistance(JNIEnv* env, jobject glonassAssistanceObj,
+ GlonassAssistance& galileoAssistance) {
+ jobject glonassAlmanacObj =
+ env->CallObjectMethod(glonassAssistanceObj, method_glonassAssistanceGetAlmanac);
+ jobject utcModelObj =
+ env->CallObjectMethod(glonassAssistanceObj, method_glonassAssistanceGetUtcModel);
+ jobject timeModelsObj =
+ env->CallObjectMethod(glonassAssistanceObj, method_glonassAssistanceGetTimeModels);
+ jobject satelliteEphemerisObj =
+ env->CallObjectMethod(glonassAssistanceObj,
+ method_glonassAssistanceGetSatelliteEphemeris);
+ jobject satelliteCorrectionsObj =
+ env->CallObjectMethod(glonassAssistanceObj,
+ method_glonassAssistanceGetSatelliteCorrections);
+ setGlonassAlmanac(env, glonassAlmanacObj, galileoAssistance.almanac);
+ setUtcModel(env, utcModelObj, galileoAssistance.utcModel);
+ setTimeModels(env, timeModelsObj, galileoAssistance.timeModels);
+ setGlonassSatelliteEphemeris(env, satelliteEphemerisObj, galileoAssistance.satelliteEphemeris);
+ setSatelliteCorrections(env, satelliteCorrectionsObj, galileoAssistance.satelliteCorrections);
+ env->DeleteLocalRef(glonassAlmanacObj);
+ env->DeleteLocalRef(utcModelObj);
+ env->DeleteLocalRef(timeModelsObj);
+ env->DeleteLocalRef(satelliteEphemerisObj);
+ env->DeleteLocalRef(satelliteCorrectionsObj);
+}
+
+void GnssAssistanceUtil::setGlonassAlmanac(JNIEnv* env, jobject glonassAlmanacObj,
+ GlonassAlmanac& glonassAlmanac) {
+ if (glonassAlmanacObj == nullptr) {
+ glonassAlmanac.issueDateMs = -1;
+ return;
+ }
+ jlong issueDateMillis =
+ env->CallLongMethod(glonassAlmanacObj, method_glonassAlmanacGetIssueDateMillis);
+ glonassAlmanac.issueDateMs = issueDateMillis;
+ jobject satelliteAlmanacsObj =
+ env->CallObjectMethod(glonassAlmanacObj, method_glonassAlmanacGetSatelliteAlmanacs);
+ if (satelliteAlmanacsObj == nullptr) return;
+ auto len = env->CallIntMethod(satelliteAlmanacsObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject glonassSatelliteAlmanacObj =
+ env->CallObjectMethod(satelliteAlmanacsObj, method_listGet, i);
+ if (glonassSatelliteAlmanacObj == nullptr) continue;
+ GlonassSatelliteAlmanac glonassSatelliteAlmanac;
+ jdouble deltaI = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetDeltaI);
+ glonassSatelliteAlmanac.deltaI = deltaI;
+ jdouble deltaT = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetDeltaT);
+ glonassSatelliteAlmanac.deltaT = deltaT;
+ jdouble deltaTDot = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetDeltaTDot);
+ glonassSatelliteAlmanac.deltaTDot = deltaTDot;
+ jdouble eccentricity = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetEccentricity);
+ glonassSatelliteAlmanac.eccentricity = eccentricity;
+ jint frequencyChannelNumber =
+ env->CallIntMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetFrequencyChannelNumber);
+ glonassSatelliteAlmanac.frequencyChannelNumber =
+ static_cast<int32_t>(frequencyChannelNumber);
+ jdouble lambda = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetLambda);
+ glonassSatelliteAlmanac.lambda = lambda;
+ jdouble omega = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetOmega);
+ glonassSatelliteAlmanac.omega = omega;
+ jint slotNumber = env->CallIntMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetSlotNumber);
+ glonassSatelliteAlmanac.slotNumber = static_cast<int32_t>(slotNumber);
+ jint healthState = env->CallIntMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetHealthState);
+ glonassSatelliteAlmanac.svHealth = static_cast<int32_t>(healthState);
+ jdouble tLambda = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetTLambda);
+ glonassSatelliteAlmanac.tLambda = tLambda;
+ jdouble tau = env->CallDoubleMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetTau);
+ glonassSatelliteAlmanac.tau = tau;
+ jboolean isGlonassM = env->CallBooleanMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetIsGlonassM);
+ glonassSatelliteAlmanac.isGlonassM = isGlonassM;
+ jint calendarDayNumber =
+ env->CallIntMethod(glonassSatelliteAlmanacObj,
+ method_glonassSatelliteAlmanacGetCalendarDayNumber);
+ glonassSatelliteAlmanac.calendarDayNumber = static_cast<int32_t>(calendarDayNumber);
+ glonassAlmanac.satelliteAlmanacs.push_back(glonassSatelliteAlmanac);
+ env->DeleteLocalRef(glonassSatelliteAlmanacObj);
+ }
+ env->DeleteLocalRef(satelliteAlmanacsObj);
+}
+
+void GnssAssistanceUtil::setGlonassSatelliteEphemeris(
+ JNIEnv* env, jobject glonassSatelliteEphemerisListObj,
+ std::vector<GlonassSatelliteEphemeris>& glonassSatelliteEphemerisList) {
+ if (glonassSatelliteEphemerisListObj == nullptr) return;
+ auto len = env->CallIntMethod(glonassSatelliteEphemerisListObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject glonassSatelliteEphemerisObj =
+ env->CallObjectMethod(glonassSatelliteEphemerisListObj, method_listGet, i);
+ if (glonassSatelliteEphemerisObj == nullptr) continue;
+ GlonassSatelliteEphemeris glonassSatelliteEphemeris;
+ jdouble ageInDays = env->CallDoubleMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetAgeInDays);
+ glonassSatelliteEphemeris.ageInDays = ageInDays;
+
+ // Set the GlonassSatelliteClockModel.
+ jobject glonassSatelliteClockModelObj =
+ env->CallObjectMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetSatelliteClockModel);
+ GlonassSatelliteClockModel glonassSatelliteClockModel;
+ jdouble clockBias = env->CallDoubleMethod(glonassSatelliteClockModelObj,
+ method_glonassSatelliteClockModelGetClockBias);
+ glonassSatelliteClockModel.clockBias = clockBias;
+ jdouble frequencyBias =
+ env->CallDoubleMethod(glonassSatelliteClockModelObj,
+ method_glonassSatelliteClockModelGetFrequencyBias);
+ glonassSatelliteClockModel.frequencyBias = frequencyBias;
+ jint frequencyChannelNumber =
+ env->CallIntMethod(glonassSatelliteClockModelObj,
+ method_glonassSatelliteClockModelGetFrequencyChannelNumber);
+ glonassSatelliteClockModel.frequencyChannelNumber =
+ static_cast<int32_t>(frequencyChannelNumber);
+ jdouble timeOfClockSeconds =
+ env->CallDoubleMethod(glonassSatelliteClockModelObj,
+ method_glonassSatelliteClockModelGetTimeOfClockSeconds);
+ glonassSatelliteClockModel.timeOfClockSeconds = timeOfClockSeconds;
+ glonassSatelliteEphemeris.satelliteClockModel = glonassSatelliteClockModel;
+ env->DeleteLocalRef(glonassSatelliteClockModelObj);
+
+ // Set the GlonassSatelliteOrbitModel.
+ jobject glonassSatelliteOrbitModelObj =
+ env->CallObjectMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetSatelliteOrbitModel);
+ GlonassSatelliteOrbitModel glonassSatelliteOrbitModel;
+ jdouble x = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetX);
+ glonassSatelliteOrbitModel.x = x;
+ jdouble y = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetY);
+ glonassSatelliteOrbitModel.y = y;
+ jdouble z = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetZ);
+ glonassSatelliteOrbitModel.z = z;
+ jdouble xAccel = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetXAccel);
+ glonassSatelliteOrbitModel.xAccel = xAccel;
+ jdouble yAccel = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetYAccel);
+ glonassSatelliteOrbitModel.yAccel = yAccel;
+ jdouble zAccel = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetZAccel);
+ glonassSatelliteOrbitModel.zAccel = zAccel;
+ jdouble xDot = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetXDot);
+ glonassSatelliteOrbitModel.xDot = xDot;
+ jdouble yDot = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetYDot);
+ glonassSatelliteOrbitModel.yDot = yDot;
+ jdouble zDot = env->CallDoubleMethod(glonassSatelliteOrbitModelObj,
+ method_glonassSatelliteOrbitModelGetZDot);
+ glonassSatelliteOrbitModel.zDot = zDot;
+ glonassSatelliteEphemeris.satelliteOrbitModel = glonassSatelliteOrbitModel;
+ env->DeleteLocalRef(glonassSatelliteOrbitModelObj);
+
+ jint healthState = env->CallIntMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetHealthState);
+ glonassSatelliteEphemeris.svHealth = static_cast<int32_t>(healthState);
+ jint slotNumber = env->CallIntMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetSlotNumber);
+ glonassSatelliteEphemeris.slotNumber = static_cast<int32_t>(slotNumber);
+ jdouble frameTimeSeconds =
+ env->CallDoubleMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetFrameTimeSeconds);
+ glonassSatelliteEphemeris.frameTimeSeconds = frameTimeSeconds;
+ jint updateIntervalMinutes =
+ env->CallIntMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetUpdateIntervalMinutes);
+ glonassSatelliteEphemeris.updateIntervalMinutes =
+ static_cast<int32_t>(updateIntervalMinutes);
+ jboolean isGlonassM = env->CallBooleanMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetIsGlonassM);
+ glonassSatelliteEphemeris.isGlonassM = isGlonassM;
+ jboolean isUpdateIntervalOdd =
+ env->CallBooleanMethod(glonassSatelliteEphemerisObj,
+ method_glonassSatelliteEphemerisGetIsUpdateIntervalOdd);
+ glonassSatelliteEphemeris.isOddUpdateInterval = isUpdateIntervalOdd;
+ glonassSatelliteEphemerisList.push_back(glonassSatelliteEphemeris);
+ env->DeleteLocalRef(glonassSatelliteEphemerisObj);
+ }
+}
+
+void GnssAssistanceUtil::setGalileoAssistance(JNIEnv* env, jobject galileoAssistanceObj,
+ GalileoAssistance& galileoAssistance) {
+ jobject galileoAlmanacObj =
+ env->CallObjectMethod(galileoAssistanceObj, method_galileoAssistanceGetAlmanac);
+ jobject ionosphericModelObj =
+ env->CallObjectMethod(galileoAssistanceObj,
+ method_galileoAssistanceGetIonosphericModel);
+ jobject utcModelObj =
+ env->CallObjectMethod(galileoAssistanceObj, method_galileoAssistanceGetUtcModel);
+ jobject leapSecondsModelObj =
+ env->CallObjectMethod(galileoAssistanceObj,
+ method_galileoAssistanceGetLeapSecondsModel);
+ jobject timeModelsObj =
+ env->CallObjectMethod(galileoAssistanceObj, method_galileoAssistanceGetTimeModels);
+ jobject satelliteEphemerisObj =
+ env->CallObjectMethod(galileoAssistanceObj,
+ method_galileoAssistanceGetSatelliteEphemeris);
+ jobject realTimeIntegrityModelsObj =
+ env->CallObjectMethod(galileoAssistanceObj,
+ method_galileoAssistanceGetRealTimeIntegrityModels);
+ jobject satelliteCorrectionsObj =
+ env->CallObjectMethod(galileoAssistanceObj,
+ method_galileoAssistanceGetSatelliteCorrections);
+ setGnssAlmanac(env, galileoAlmanacObj, galileoAssistance.almanac);
+ setGaliloKlobucharIonosphericModel(env, ionosphericModelObj,
+ galileoAssistance.ionosphericModel);
+ setUtcModel(env, utcModelObj, galileoAssistance.utcModel);
+ setLeapSecondsModel(env, leapSecondsModelObj, galileoAssistance.leapSecondsModel);
+ setTimeModels(env, timeModelsObj, galileoAssistance.timeModels);
+ setGalileoSatelliteEphemeris(env, satelliteEphemerisObj, galileoAssistance.satelliteEphemeris);
+ setRealTimeIntegrityModels(env, realTimeIntegrityModelsObj,
+ galileoAssistance.realTimeIntegrityModels);
+ setSatelliteCorrections(env, satelliteCorrectionsObj, galileoAssistance.satelliteCorrections);
+ env->DeleteLocalRef(galileoAlmanacObj);
+ env->DeleteLocalRef(ionosphericModelObj);
+ env->DeleteLocalRef(utcModelObj);
+ env->DeleteLocalRef(leapSecondsModelObj);
+ env->DeleteLocalRef(timeModelsObj);
+ env->DeleteLocalRef(satelliteEphemerisObj);
+ env->DeleteLocalRef(realTimeIntegrityModelsObj);
+ env->DeleteLocalRef(satelliteCorrectionsObj);
+}
+
+void GnssAssistanceUtil::setGaliloKlobucharIonosphericModel(
+ JNIEnv* env, jobject galileoIonosphericModelObj,
+ GalileoIonosphericModel& ionosphericModel) {
+ if (galileoIonosphericModelObj == nullptr) return;
+ jdouble ai0 =
+ env->CallDoubleMethod(galileoIonosphericModelObj, method_galileoIonosphericModelGetAi0);
+ ionosphericModel.ai0 = ai0;
+ jdouble ai1 =
+ env->CallDoubleMethod(galileoIonosphericModelObj, method_galileoIonosphericModelGetAi1);
+ ionosphericModel.ai1 = ai1;
+ jdouble ai2 =
+ env->CallDoubleMethod(galileoIonosphericModelObj, method_galileoIonosphericModelGetAi2);
+ ionosphericModel.ai2 = ai2;
+}
+
+void GnssAssistanceUtil::setGalileoSatelliteEphemeris(
+ JNIEnv* env, jobject galileoSatelliteEphemerisListObj,
+ std::vector<GalileoSatelliteEphemeris>& galileoSatelliteEphemerisList) {
+ if (galileoSatelliteEphemerisListObj == nullptr) return;
+ auto len = env->CallIntMethod(galileoSatelliteEphemerisListObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject galileoSatelliteEphemerisObj =
+ env->CallObjectMethod(galileoSatelliteEphemerisListObj, method_listGet, i);
+ GalileoSatelliteEphemeris galileoSatelliteEphemeris;
+ GalileoSvHealth galileoSvHealth;
+ // Set the svid of the satellite.
+ jint svid = env->CallLongMethod(galileoSatelliteEphemerisObj,
+ method_galileoSatelliteEphemerisGetSvid);
+ galileoSatelliteEphemeris.svid = svid;
+
+ // Set the satellite clock models.
+ jobject galileoSatelliteClockModelListObj =
+ env->CallObjectMethod(galileoSatelliteEphemerisObj,
+ method_galileoSatelliteEphemerisGetSatelliteClockModels);
+ auto size = env->CallIntMethod(galileoSatelliteClockModelListObj, method_listSize);
+ for (uint16_t j = 0; j < size; ++j) {
+ jobject galileoSatelliteClockModelObj =
+ env->CallObjectMethod(galileoSatelliteClockModelListObj, method_listGet, j);
+ if (galileoSatelliteClockModelObj == nullptr) continue;
+ GalileoSatelliteClockModel galileoSatelliteClockModel;
+ jdouble af0 = env->CallDoubleMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetAf0);
+ galileoSatelliteClockModel.af0 = af0;
+ jdouble af1 = env->CallDoubleMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetAf1);
+ galileoSatelliteClockModel.af1 = af1;
+ jdouble af2 = env->CallDoubleMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetAf2);
+ galileoSatelliteClockModel.af2 = af2;
+ jdouble bgdSeconds =
+ env->CallDoubleMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetBgdSeconds);
+ galileoSatelliteClockModel.bgdSeconds = bgdSeconds;
+ jint satelliteClockType =
+ env->CallIntMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetSatelliteClockType);
+ galileoSatelliteClockModel.satelliteClockType =
+ static_cast<GalileoSatelliteClockModel::SatelliteClockType>(satelliteClockType);
+ jdouble sisaMeters =
+ env->CallDoubleMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetSisaMeters);
+ galileoSatelliteClockModel.sisaMeters = sisaMeters;
+ jdouble timeOfClockSeconds =
+ env->CallDoubleMethod(galileoSatelliteClockModelObj,
+ method_galileoSatelliteClockModelGetTimeOfClockSeconds);
+ galileoSatelliteClockModel.timeOfClockSeconds = timeOfClockSeconds;
+ galileoSatelliteEphemeris.satelliteClockModel.push_back(galileoSatelliteClockModel);
+ env->DeleteLocalRef(galileoSatelliteClockModelObj);
+ }
+ env->DeleteLocalRef(galileoSatelliteClockModelListObj);
+
+ // Set the satelliteOrbitModel of the satellite.
+ jobject satelliteOrbitModelObj =
+ env->CallObjectMethod(galileoSatelliteEphemerisObj,
+ method_galileoSatelliteEphemerisGetSatelliteOrbitModel);
+ GnssAssistanceUtil::setKeplerianOrbitModel(env, satelliteOrbitModelObj,
+ galileoSatelliteEphemeris.satelliteOrbitModel);
+ env->DeleteLocalRef(satelliteOrbitModelObj);
+
+ // Set the satellite health of the satellite clock model.
+ jobject galileoSvHealthObj =
+ env->CallObjectMethod(galileoSatelliteEphemerisObj,
+ method_galileoSatelliteEphemerisGetSatelliteHealth);
+ jint dataValidityStatusE1b =
+ env->CallIntMethod(galileoSvHealthObj,
+ method_galileoSvHealthGetDataValidityStatusE1b);
+ galileoSvHealth.dataValidityStatusE1b =
+ static_cast<GalileoSvHealth::GalileoHealthDataVaidityType>(dataValidityStatusE1b);
+ jint dataValidityStatusE5a =
+ env->CallIntMethod(galileoSvHealthObj,
+ method_galileoSvHealthGetDataValidityStatusE5a);
+ galileoSvHealth.dataValidityStatusE5a =
+ static_cast<GalileoSvHealth::GalileoHealthDataVaidityType>(dataValidityStatusE5a);
+ jint dataValidityStatusE5b =
+ env->CallIntMethod(galileoSvHealthObj,
+ method_galileoSvHealthGetDataValidityStatusE5b);
+ galileoSvHealth.dataValidityStatusE5b =
+ static_cast<GalileoSvHealth::GalileoHealthDataVaidityType>(dataValidityStatusE5b);
+ jint signalHealthStatusE1b =
+ env->CallIntMethod(galileoSvHealthObj,
+ method_galileoSvHealthGetSignalHealthStatusE1b);
+ galileoSvHealth.signalHealthStatusE1b =
+ static_cast<GalileoSvHealth::GalileoHealthStatusType>(signalHealthStatusE1b);
+ jint signalHealthStatusE5a =
+ env->CallIntMethod(galileoSvHealthObj,
+ method_galileoSvHealthGetSignalHealthStatusE5a);
+ galileoSvHealth.signalHealthStatusE5a =
+ static_cast<GalileoSvHealth::GalileoHealthStatusType>(signalHealthStatusE5a);
+ jint signalHealthStatusE5b =
+ env->CallIntMethod(galileoSvHealthObj,
+ method_galileoSvHealthGetSignalHealthStatusE5b);
+ galileoSvHealth.signalHealthStatusE5b =
+ static_cast<GalileoSvHealth::GalileoHealthStatusType>(signalHealthStatusE5b);
+ galileoSatelliteEphemeris.svHealth = galileoSvHealth;
+ env->DeleteLocalRef(galileoSvHealthObj);
+
+ // Set the satelliteEphemerisTime of the satellite.
+ jobject satelliteEphemerisTimeObj =
+ env->CallObjectMethod(galileoSatelliteEphemerisObj,
+ method_galileoSatelliteEphemerisGetSatelliteEphemerisTime);
+ GnssAssistanceUtil::setSatelliteEphemerisTime(env, satelliteEphemerisTimeObj,
+ galileoSatelliteEphemeris
+ .satelliteEphemerisTime);
+ env->DeleteLocalRef(satelliteEphemerisTimeObj);
+
+ galileoSatelliteEphemerisList.push_back(galileoSatelliteEphemeris);
+ env->DeleteLocalRef(galileoSatelliteEphemerisObj);
+ }
+}
+
+void GnssAssistanceUtil::setBeidouAssistance(JNIEnv* env, jobject beidouAssistanceObj,
+ BeidouAssistance& beidouAssistance) {
+ jobject beidouAlmanacObj =
+ env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetAlmanac);
+ jobject ionosphericModelObj =
+ env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetIonosphericModel);
+ jobject utcModelObj =
+ env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetUtcModel);
+ jobject leapSecondsModelObj =
+ env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetLeapSecondsModel);
+ jobject timeModelsObj =
+ env->CallObjectMethod(beidouAssistanceObj, method_beidouAssistanceGetTimeModels);
+ jobject satelliteEphemerisObj =
+ env->CallObjectMethod(beidouAssistanceObj,
+ method_beidouAssistanceGetSatelliteEphemeris);
+ jobject realTimeIntegrityModelsObj =
+ env->CallObjectMethod(beidouAssistanceObj,
+ method_beidouAssistanceGetRealTimeIntegrityModels);
+ jobject satelliteCorrectionsObj =
+ env->CallObjectMethod(beidouAssistanceObj,
+ method_beidouAssistanceGetSatelliteCorrections);
+ setGnssAlmanac(env, beidouAlmanacObj, beidouAssistance.almanac);
+ setKlobucharIonosphericModel(env, ionosphericModelObj, beidouAssistance.ionosphericModel);
+ setUtcModel(env, utcModelObj, beidouAssistance.utcModel);
+ setLeapSecondsModel(env, leapSecondsModelObj, beidouAssistance.leapSecondsModel);
+ setTimeModels(env, timeModelsObj, beidouAssistance.timeModels);
+ setBeidouSatelliteEphemeris(env, satelliteEphemerisObj, beidouAssistance.satelliteEphemeris);
+ setRealTimeIntegrityModels(env, realTimeIntegrityModelsObj,
+ beidouAssistance.realTimeIntegrityModels);
+ setSatelliteCorrections(env, satelliteCorrectionsObj, beidouAssistance.satelliteCorrections);
+ env->DeleteLocalRef(beidouAlmanacObj);
+ env->DeleteLocalRef(ionosphericModelObj);
+ env->DeleteLocalRef(utcModelObj);
+ env->DeleteLocalRef(leapSecondsModelObj);
+ env->DeleteLocalRef(timeModelsObj);
+ env->DeleteLocalRef(satelliteEphemerisObj);
+ env->DeleteLocalRef(realTimeIntegrityModelsObj);
+ env->DeleteLocalRef(satelliteCorrectionsObj);
+}
+
+void GnssAssistanceUtil::setBeidouSatelliteEphemeris(
+ JNIEnv* env, jobject beidouSatelliteEphemerisListObj,
+ std::vector<BeidouSatelliteEphemeris>& beidouSatelliteEphemerisList) {
+ if (beidouSatelliteEphemerisListObj == nullptr) return;
+ auto len = env->CallIntMethod(beidouSatelliteEphemerisListObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject beidouSatelliteEphemerisObj =
+ env->CallObjectMethod(beidouSatelliteEphemerisListObj, method_listGet, i);
+ if (beidouSatelliteEphemerisObj == nullptr) continue;
+ BeidouSatelliteEphemeris beidouSatelliteEphemeris;
+
+ // Set the svid of the satellite.
+ jint svid = env->CallIntMethod(beidouSatelliteEphemerisObj,
+ method_beidouSatelliteEphemerisGetSvid);
+ beidouSatelliteEphemeris.svid = static_cast<int32_t>(svid);
+
+ // Set the satelliteClockModel of the satellite.
+ jobject satelliteClockModelObj =
+ env->CallObjectMethod(beidouSatelliteEphemerisObj,
+ method_beidouSatelliteEphemerisGetSatelliteClockModel);
+ jdouble af0 = env->CallDoubleMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetAf0);
+ jdouble af1 = env->CallDoubleMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetAf1);
+ jdouble af2 = env->CallDoubleMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetAf2);
+ jdouble tgd1 = env->CallDoubleMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetTgd1);
+ jdouble tgd2 = env->CallDoubleMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetTgd2);
+ jdouble aodc = env->CallDoubleMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetAodc);
+ jlong timeOfClockSeconds =
+ env->CallLongMethod(satelliteClockModelObj,
+ method_beidouSatelliteClockModelGetTimeOfClockSeconds);
+ beidouSatelliteEphemeris.satelliteClockModel.af0 = af0;
+ beidouSatelliteEphemeris.satelliteClockModel.af1 = af1;
+ beidouSatelliteEphemeris.satelliteClockModel.af2 = af2;
+ beidouSatelliteEphemeris.satelliteClockModel.tgd1 = tgd1;
+ beidouSatelliteEphemeris.satelliteClockModel.tgd2 = tgd2;
+ beidouSatelliteEphemeris.satelliteClockModel.aodc = aodc;
+ beidouSatelliteEphemeris.satelliteClockModel.timeOfClockSeconds = timeOfClockSeconds;
+ env->DeleteLocalRef(satelliteClockModelObj);
+
+ // Set the satelliteOrbitModel of the satellite.
+ jobject satelliteOrbitModelObj =
+ env->CallObjectMethod(beidouSatelliteEphemerisObj,
+ method_beidouSatelliteEphemerisGetSatelliteOrbitModel);
+ GnssAssistanceUtil::setKeplerianOrbitModel(env, satelliteOrbitModelObj,
+ beidouSatelliteEphemeris.satelliteOrbitModel);
+ env->DeleteLocalRef(satelliteOrbitModelObj);
+
+ // Set the satelliteHealth of the satellite.
+ jobject satelliteHealthObj =
+ env->CallObjectMethod(beidouSatelliteEphemerisObj,
+ method_beidouSatelliteEphemerisGetSatelliteHealth);
+ jint satH1 = env->CallIntMethod(satelliteHealthObj, method_beidouSatelliteHealthGetSatH1);
+ jint svAccur =
+ env->CallIntMethod(satelliteHealthObj, method_beidouSatelliteHealthGetSvAccur);
+ beidouSatelliteEphemeris.satelliteHealth.satH1 = static_cast<int32_t>(satH1);
+ beidouSatelliteEphemeris.satelliteHealth.svAccur = static_cast<int32_t>(svAccur);
+ env->DeleteLocalRef(satelliteHealthObj);
+
+ // Set the satelliteEphemerisTime of the satellite.
+ jobject satelliteEphemerisTimeObj =
+ env->CallObjectMethod(beidouSatelliteEphemerisObj,
+ method_beidouSatelliteEphemerisGetSatelliteEphemerisTime);
+ jint iode = env->CallIntMethod(satelliteEphemerisTimeObj,
+ method_beidouSatelliteEphemerisTimeGetIode);
+ jint beidouWeekNumber =
+ env->CallIntMethod(satelliteEphemerisTimeObj,
+ method_beidouSatelliteEphemerisTimeGetBeidouWeekNumber);
+ jint toeSeconds = env->CallDoubleMethod(satelliteEphemerisTimeObj,
+ method_beidouSatelliteEphemerisTimeGetToeSeconds);
+ beidouSatelliteEphemeris.satelliteEphemerisTime.aode = static_cast<int32_t>(iode);
+ beidouSatelliteEphemeris.satelliteEphemerisTime.weekNumber =
+ static_cast<int32_t>(beidouWeekNumber);
+ beidouSatelliteEphemeris.satelliteEphemerisTime.toeSeconds =
+ static_cast<int32_t>(toeSeconds);
+ env->DeleteLocalRef(satelliteEphemerisTimeObj);
+
+ beidouSatelliteEphemerisList.push_back(beidouSatelliteEphemeris);
+ env->DeleteLocalRef(beidouSatelliteEphemerisObj);
+ }
+}
+
+void GnssAssistanceUtil::setGpsAssistance(JNIEnv* env, jobject gpsAssistanceObj,
+ GpsAssistance& gpsAssistance) {
+ jobject gnssAlmanacObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetAlmanac);
+ jobject ionosphericModelObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetIonosphericModel);
+ jobject utcModelObj = env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetUtcModel);
+ jobject leapSecondsModelObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetLeapSecondsModel);
+ jobject timeModelsObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetTimeModels);
+ jobject satelliteEphemerisObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetSatelliteEphemeris);
+ jobject realTimeIntegrityModelsObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetRealTimeIntegrityModels);
+ jobject satelliteCorrectionsObj =
+ env->CallObjectMethod(gpsAssistanceObj, method_gpsAssistanceGetSatelliteCorrections);
+
+ setGnssAlmanac(env, gnssAlmanacObj, gpsAssistance.almanac);
+ setKlobucharIonosphericModel(env, ionosphericModelObj, gpsAssistance.ionosphericModel);
+ setUtcModel(env, utcModelObj, gpsAssistance.utcModel);
+ setLeapSecondsModel(env, leapSecondsModelObj, gpsAssistance.leapSecondsModel);
+ setTimeModels(env, timeModelsObj, gpsAssistance.timeModels);
+ setGpsOrQzssSatelliteEphemeris<GpsSatelliteEphemeris>(env, satelliteEphemerisObj,
+ gpsAssistance.satelliteEphemeris);
+ setRealTimeIntegrityModels(env, realTimeIntegrityModelsObj,
+ gpsAssistance.realTimeIntegrityModels);
+ setSatelliteCorrections(env, satelliteCorrectionsObj, gpsAssistance.satelliteCorrections);
+ env->DeleteLocalRef(gnssAlmanacObj);
+ env->DeleteLocalRef(ionosphericModelObj);
+ env->DeleteLocalRef(utcModelObj);
+ env->DeleteLocalRef(leapSecondsModelObj);
+ env->DeleteLocalRef(timeModelsObj);
+ env->DeleteLocalRef(satelliteEphemerisObj);
+ env->DeleteLocalRef(realTimeIntegrityModelsObj);
+ env->DeleteLocalRef(satelliteCorrectionsObj);
+}
+
+/** Set the GPS/QZSS satellite ephemeris list. */
+template <class T>
+void GnssAssistanceUtil::setGpsOrQzssSatelliteEphemeris(JNIEnv* env,
+ jobject satelliteEphemerisListObj,
+ std::vector<T>& satelliteEphemerisList) {
+ if (satelliteEphemerisListObj == nullptr) return;
+ auto len = env->CallIntMethod(satelliteEphemerisListObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject satelliteEphemerisObj =
+ env->CallObjectMethod(satelliteEphemerisListObj, method_listGet, i);
+ if (satelliteEphemerisObj == nullptr) continue;
+ T satelliteEphemeris;
+ // Set the svid of the satellite.
+ jint svid = env->CallIntMethod(satelliteEphemerisObj, method_gpsSatelliteEphemerisGetSvid);
+ satelliteEphemeris.svid = static_cast<int32_t>(svid);
+
+ // Set the gpsL2Params of the satellite.
+ jobject gpsL2ParamsObj = env->CallObjectMethod(satelliteEphemerisObj,
+ method_gpsSatelliteEphemerisGetGpsL2Params);
+ jint l2Code = env->CallIntMethod(gpsL2ParamsObj, method_gpsL2ParamsGetL2Code);
+ jint l2Flag = env->CallIntMethod(gpsL2ParamsObj, method_gpsL2ParamsGetL2Flag);
+ satelliteEphemeris.gpsL2Params.l2Code = static_cast<int32_t>(l2Code);
+ satelliteEphemeris.gpsL2Params.l2Flag = static_cast<int32_t>(l2Flag);
+ env->DeleteLocalRef(gpsL2ParamsObj);
+
+ // Set the satelliteClockModel of the satellite.
+ jobject satelliteClockModelObj =
+ env->CallObjectMethod(satelliteEphemerisObj,
+ method_gpsSatelliteEphemerisGetSatelliteClockModel);
+ jdouble af0 =
+ env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetAf0);
+ jdouble af1 =
+ env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetAf1);
+ jdouble af2 =
+ env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetAf2);
+ jdouble tgd =
+ env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetTgd);
+ jint iodc =
+ env->CallDoubleMethod(satelliteClockModelObj, method_gpsSatelliteClockModelGetIodc);
+ jlong timeOfClockSeconds =
+ env->CallLongMethod(satelliteClockModelObj,
+ method_gpsSatelliteClockModelGetTimeOfClockSeconds);
+ satelliteEphemeris.satelliteClockModel.af0 = af0;
+ satelliteEphemeris.satelliteClockModel.af1 = af1;
+ satelliteEphemeris.satelliteClockModel.af2 = af2;
+ satelliteEphemeris.satelliteClockModel.tgd = tgd;
+ satelliteEphemeris.satelliteClockModel.iodc = static_cast<int32_t>(iodc);
+ satelliteEphemeris.satelliteClockModel.timeOfClockSeconds = timeOfClockSeconds;
+ env->DeleteLocalRef(satelliteClockModelObj);
+
+ // Set the satelliteOrbitModel of the satellite.
+ jobject satelliteOrbitModelObj =
+ env->CallObjectMethod(satelliteEphemerisObj,
+ method_gpsSatelliteEphemerisGetSatelliteOrbitModel);
+ GnssAssistanceUtil::setKeplerianOrbitModel(env, satelliteOrbitModelObj,
+ satelliteEphemeris.satelliteOrbitModel);
+ env->DeleteLocalRef(satelliteOrbitModelObj);
+
+ // Set the satelliteHealth of the satellite.
+ jobject satelliteHealthObj =
+ env->CallObjectMethod(satelliteEphemerisObj,
+ method_gpsSatelliteEphemerisGetSatelliteHealth);
+ jint svHealth =
+ env->CallIntMethod(satelliteHealthObj, method_gpsSatelliteHealthGetSvHealth);
+ jdouble svAccur =
+ env->CallDoubleMethod(satelliteHealthObj, method_gpsSatelliteHealthGetSvAccur);
+ jdouble fitInt = env->CallIntMethod(satelliteHealthObj, method_gpsSatelliteHealthGetFitInt);
+ satelliteEphemeris.satelliteHealth.svHealth = static_cast<int32_t>(svHealth);
+ satelliteEphemeris.satelliteHealth.svAccur = svAccur;
+ satelliteEphemeris.satelliteHealth.fitInt = fitInt;
+ env->DeleteLocalRef(satelliteHealthObj);
+
+ // Set the satelliteEphemerisTime of the satellite.
+ jobject satelliteEphemerisTimeObj =
+ env->CallObjectMethod(satelliteEphemerisObj,
+ method_gpsSatelliteEphemerisGetSatelliteEphemerisTime);
+ GnssAssistanceUtil::setSatelliteEphemerisTime(env, satelliteEphemerisTimeObj,
+ satelliteEphemeris.satelliteEphemerisTime);
+ env->DeleteLocalRef(satelliteEphemerisTimeObj);
+
+ satelliteEphemerisList.push_back(satelliteEphemeris);
+ env->DeleteLocalRef(satelliteEphemerisObj);
+ }
+}
+
+void GnssAssistanceUtil::setSatelliteCorrections(
+ JNIEnv* env, jobject satelliteCorrectionsObj,
+ std::vector<GnssSatelliteCorrections>& gnssSatelliteCorrectionsList) {
+ if (satelliteCorrectionsObj == nullptr) return;
+ auto len = env->CallIntMethod(satelliteCorrectionsObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ GnssSatelliteCorrections gnssSatelliteCorrections;
+ jobject satelliteCorrectionObj =
+ env->CallObjectMethod(satelliteCorrectionsObj, method_listGet, i);
+ if (satelliteCorrectionObj == nullptr) continue;
+ jint svid = env->CallIntMethod(satelliteCorrectionObj, method_satelliteCorrectionGetSvid);
+ gnssSatelliteCorrections.svid = svid;
+ jobject ionosphericCorrectionsObj =
+ env->CallObjectMethod(satelliteCorrectionObj,
+ method_satelliteCorrectionGetIonosphericCorrections);
+ env->DeleteLocalRef(satelliteCorrectionObj);
+ auto size = env->CallIntMethod(ionosphericCorrectionsObj, method_listSize);
+ for (uint16_t j = 0; j < size; ++j) {
+ jobject ionosphericCorrectionObj =
+ env->CallObjectMethod(ionosphericCorrectionsObj, method_listGet, j);
+ if (ionosphericCorrectionObj == nullptr) continue;
+ IonosphericCorrection ionosphericCorrection;
+ jlong carrierFrequencyHz =
+ env->CallLongMethod(ionosphericCorrectionObj,
+ method_ionosphericCorrectionGetCarrierFrequencyHz);
+ ionosphericCorrection.carrierFrequencyHz = carrierFrequencyHz;
+
+ jobject gnssCorrectionComponentObj =
+ env->CallObjectMethod(ionosphericCorrectionObj,
+ method_ionosphericCorrectionGetIonosphericCorrection);
+ env->DeleteLocalRef(ionosphericCorrectionObj);
+
+ jstring sourceKey = static_cast<jstring>(
+ env->CallObjectMethod(gnssCorrectionComponentObj,
+ method_gnssCorrectionComponentGetSourceKey));
+ ScopedJniString jniSourceKey{env, sourceKey};
+ ionosphericCorrection.ionosphericCorrectionComponent.sourceKey =
+ android::String16(jniSourceKey.c_str());
+
+ jobject pseudorangeCorrectionObj =
+ env->CallObjectMethod(gnssCorrectionComponentObj,
+ method_gnssCorrectionComponentGetPseudorangeCorrection);
+ jdouble correctionMeters =
+ env->CallDoubleMethod(pseudorangeCorrectionObj,
+ method_pseudorangeCorrectionGetCorrectionMeters);
+ jdouble correctionUncertaintyMeters = env->CallDoubleMethod(
+ pseudorangeCorrectionObj,
+ method_pseudorangeCorrectionGetCorrectionUncertaintyMeters);
+ jdouble correctionRateMetersPerSecond = env->CallDoubleMethod(
+ pseudorangeCorrectionObj,
+ method_pseudorangeCorrectionGetCorrectionRateMetersPerSecond);
+ ionosphericCorrection.ionosphericCorrectionComponent.pseudorangeCorrection
+ .correctionMeters = correctionMeters;
+ ionosphericCorrection.ionosphericCorrectionComponent.pseudorangeCorrection
+ .correctionUncertaintyMeters = correctionUncertaintyMeters;
+ ionosphericCorrection.ionosphericCorrectionComponent.pseudorangeCorrection
+ .correctionRateMetersPerSecond = correctionRateMetersPerSecond;
+ env->DeleteLocalRef(pseudorangeCorrectionObj);
+
+ jobject gnssIntervalObj =
+ env->CallObjectMethod(gnssCorrectionComponentObj,
+ method_gnssCorrectionComponentGetValidityInterval);
+ jdouble startMillisSinceGpsEpoch =
+ env->CallDoubleMethod(gnssIntervalObj,
+ method_gnssIntervalGetStartMillisSinceGpsEpoch);
+ jdouble endMillisSinceGpsEpoch =
+ env->CallDoubleMethod(gnssIntervalObj,
+ method_gnssIntervalGetEndMillisSinceGpsEpoch);
+ ionosphericCorrection.ionosphericCorrectionComponent.validityInterval
+ .startMillisSinceGpsEpoch = startMillisSinceGpsEpoch;
+ ionosphericCorrection.ionosphericCorrectionComponent.validityInterval
+ .endMillisSinceGpsEpoch = endMillisSinceGpsEpoch;
+ env->DeleteLocalRef(gnssIntervalObj);
+
+ env->DeleteLocalRef(gnssCorrectionComponentObj);
+ gnssSatelliteCorrections.ionosphericCorrections.push_back(ionosphericCorrection);
+ }
+ gnssSatelliteCorrectionsList.push_back(gnssSatelliteCorrections);
+ env->DeleteLocalRef(ionosphericCorrectionsObj);
+ }
+}
+
+void GnssAssistanceUtil::setRealTimeIntegrityModels(
+ JNIEnv* env, jobject realTimeIntegrityModelsObj,
+ std::vector<RealTimeIntegrityModel>& realTimeIntegrityModels) {
+ if (realTimeIntegrityModelsObj == nullptr) return;
+ auto len = env->CallIntMethod(realTimeIntegrityModelsObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject realTimeIntegrityModelObj =
+ env->CallObjectMethod(realTimeIntegrityModelsObj, method_listGet, i);
+ if (realTimeIntegrityModelObj == nullptr) continue;
+ RealTimeIntegrityModel realTimeIntegrityModel;
+ jint badSvid = env->CallIntMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetBadSvid);
+ jobject badSignalTypesObj =
+ env->CallObjectMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetBadSignalTypes);
+ auto badSignalTypesSize = env->CallIntMethod(badSignalTypesObj, method_listSize);
+ for (uint16_t j = 0; j < badSignalTypesSize; ++j) {
+ GnssSignalType badSignalType;
+ jobject badSignalTypeObj = env->CallObjectMethod(badSignalTypesObj, method_listGet, j);
+ if (badSignalTypeObj != nullptr) {
+ setGnssSignalType(env, badSignalTypeObj, badSignalType);
+ realTimeIntegrityModel.badSignalTypes.push_back(badSignalType);
+ env->DeleteLocalRef(badSignalTypeObj);
+ }
+ }
+
+ jlong startDateSeconds =
+ env->CallLongMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetStartDateSeconds);
+ jlong endDateSeconds = env->CallLongMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetEndDateSeconds);
+ jlong publishDateSeconds =
+ env->CallLongMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetPublishDateSeconds);
+ jstring advisoryNumber = static_cast<jstring>(
+ env->CallObjectMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetAdvisoryNumber));
+ ScopedJniString jniAdvisoryNumber{env, advisoryNumber};
+ jstring advisoryType = static_cast<jstring>(
+ env->CallObjectMethod(realTimeIntegrityModelObj,
+ method_realTimeIntegrityModelGetAdvisoryType));
+ ScopedJniString jniAdvisoryType{env, advisoryType};
+
+ realTimeIntegrityModel.badSvid = badSvid;
+ realTimeIntegrityModel.startDateSeconds = startDateSeconds;
+ realTimeIntegrityModel.endDateSeconds = endDateSeconds;
+ realTimeIntegrityModel.publishDateSeconds = publishDateSeconds;
+ realTimeIntegrityModel.advisoryNumber = android::String16(jniAdvisoryNumber.c_str());
+ realTimeIntegrityModel.advisoryType = android::String16(jniAdvisoryType.c_str());
+ realTimeIntegrityModels.push_back(realTimeIntegrityModel);
+ env->DeleteLocalRef(badSignalTypesObj);
+ env->DeleteLocalRef(realTimeIntegrityModelObj);
+ }
+}
+
+void GnssAssistanceUtil::setGnssSignalType(JNIEnv* env, jobject gnssSignalTypeObj,
+ GnssSignalType& gnssSignalType) {
+ if (gnssSignalTypeObj == nullptr) {
+ ALOGE("gnssSignalTypeObj is null");
+ return;
+ }
+ jint constellationType =
+ env->CallIntMethod(gnssSignalTypeObj, method_gnssSignalTypeGetConstellationType);
+ jdouble carrierFrequencyHz =
+ env->CallIntMethod(gnssSignalTypeObj, method_gnssSignalTypeGetCarrierFrequencyHz);
+ jstring codeType = static_cast<jstring>(
+ env->CallObjectMethod(gnssSignalTypeObj, method_gnssSignalTypeGetCodeType));
+ ScopedJniString jniCodeType{env, codeType};
+
+ gnssSignalType.constellation = static_cast<GnssConstellationType>(constellationType);
+ gnssSignalType.carrierFrequencyHz = static_cast<int32_t>(carrierFrequencyHz);
+ gnssSignalType.codeType = std::string(jniCodeType.c_str());
+}
+
+void GnssAssistanceUtil::setTimeModels(JNIEnv* env, jobject timeModelsObj,
+ std::vector<TimeModel>& timeModels) {
+ if (timeModelsObj == nullptr) return;
+ auto len = env->CallIntMethod(timeModelsObj, method_listSize);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject timeModelObj = env->CallObjectMethod(timeModelsObj, method_listGet, i);
+ TimeModel timeModel;
+ jint toGnss = env->CallIntMethod(timeModelObj, method_timeModelsGetToGnss);
+ jlong timeOfWeek = env->CallLongMethod(timeModelObj, method_timeModelsGetTimeOfWeek);
+ jint weekNumber = env->CallIntMethod(timeModelObj, method_timeModelsGetWeekNumber);
+ jdouble a0 = env->CallDoubleMethod(timeModelObj, method_timeModelsGetA0);
+ jdouble a1 = env->CallDoubleMethod(timeModelObj, method_timeModelsGetA1);
+ timeModel.toGnss = static_cast<GnssConstellationType>(toGnss);
+ timeModel.timeOfWeek = timeOfWeek;
+ timeModel.weekNumber = static_cast<int32_t>(weekNumber);
+ timeModel.a0 = a0;
+ timeModel.a1 = a1;
+ timeModels.push_back(timeModel);
+ env->DeleteLocalRef(timeModelObj);
+ }
+}
+
+void GnssAssistanceUtil::setLeapSecondsModel(JNIEnv* env, jobject leapSecondsModelObj,
+ LeapSecondsModel& leapSecondsModel) {
+ if (leapSecondsModelObj == nullptr) {
+ leapSecondsModel.leapSeconds = -1;
+ return;
+ }
+ jint dayNumberLeapSecondsFuture =
+ env->CallIntMethod(leapSecondsModelObj,
+ method_leapSecondsModelGetDayNumberLeapSecondsFuture);
+ jint leapSeconds =
+ env->CallIntMethod(leapSecondsModelObj, method_leapSecondsModelGetLeapSeconds);
+ jint leapSecondsFuture =
+ env->CallIntMethod(leapSecondsModelObj, method_leapSecondsModelGetLeapSecondsFuture);
+ jint weekNumberLeapSecondsFuture =
+ env->CallIntMethod(leapSecondsModelObj,
+ method_leapSecondsModelGetWeekNumberLeapSecondsFuture);
+ leapSecondsModel.dayNumberLeapSecondsFuture = static_cast<int32_t>(dayNumberLeapSecondsFuture);
+ leapSecondsModel.leapSeconds = static_cast<int32_t>(leapSeconds);
+ leapSecondsModel.leapSecondsFuture = static_cast<int32_t>(leapSecondsFuture);
+ leapSecondsModel.weekNumberLeapSecondsFuture =
+ static_cast<int32_t>(weekNumberLeapSecondsFuture);
+}
+
+void GnssAssistanceUtil::setSatelliteEphemerisTime(JNIEnv* env, jobject satelliteEphemerisTimeObj,
+ SatelliteEphemerisTime& satelliteEphemerisTime) {
+ if (satelliteEphemerisTimeObj == nullptr) return;
+ jdouble iode =
+ env->CallDoubleMethod(satelliteEphemerisTimeObj, method_satelliteEphemerisTimeGetIode);
+ jdouble toeSeconds = env->CallDoubleMethod(satelliteEphemerisTimeObj,
+ method_satelliteEphemerisTimeGetToeSeconds);
+ jint weekNumber = env->CallIntMethod(satelliteEphemerisTimeObj,
+ method_satelliteEphemerisTimeGetWeekNumber);
+ satelliteEphemerisTime.iode = iode;
+ satelliteEphemerisTime.toeSeconds = toeSeconds;
+ satelliteEphemerisTime.weekNumber = weekNumber;
+}
+
+void GnssAssistanceUtil::setKeplerianOrbitModel(JNIEnv* env, jobject keplerianOrbitModelObj,
+ KeplerianOrbitModel& keplerianOrbitModel) {
+ if (keplerianOrbitModelObj == nullptr) return;
+ jdouble rootA =
+ env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetRootA);
+ jdouble eccentricity = env->CallDoubleMethod(keplerianOrbitModelObj,
+ method_keplerianOrbitModelGetEccentricity);
+ jdouble m0 = env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetM0);
+ jdouble omega =
+ env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetOmega);
+ jdouble omegaDot =
+ env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetOmegaDot);
+ jdouble deltaN =
+ env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetDeltaN);
+ jdouble iDot = env->CallDoubleMethod(keplerianOrbitModelObj, method_keplerianOrbitModelGetIDot);
+ jobject secondOrderHarmonicPerturbationObj =
+ env->CallObjectMethod(keplerianOrbitModelObj,
+ method_keplerianOrbitModelGetSecondOrderHarmonicPerturbation);
+ jdouble cic = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj,
+ method_secondOrderHarmonicPerturbationGetCic);
+ jdouble cis = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj,
+ method_secondOrderHarmonicPerturbationGetCis);
+ jdouble crs = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj,
+ method_secondOrderHarmonicPerturbationGetCrs);
+ jdouble crc = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj,
+ method_secondOrderHarmonicPerturbationGetCrc);
+ jdouble cuc = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj,
+ method_secondOrderHarmonicPerturbationGetCuc);
+ jdouble cus = env->CallDoubleMethod(secondOrderHarmonicPerturbationObj,
+ method_secondOrderHarmonicPerturbationGetCus);
+ keplerianOrbitModel.rootA = rootA;
+ keplerianOrbitModel.eccentricity = eccentricity;
+ keplerianOrbitModel.m0 = m0;
+ keplerianOrbitModel.omega = omega;
+ keplerianOrbitModel.omegaDot = omegaDot;
+ keplerianOrbitModel.deltaN = deltaN;
+ keplerianOrbitModel.iDot = iDot;
+ keplerianOrbitModel.secondOrderHarmonicPerturbation.cic = cic;
+ keplerianOrbitModel.secondOrderHarmonicPerturbation.cis = cis;
+ keplerianOrbitModel.secondOrderHarmonicPerturbation.crs = crs;
+ keplerianOrbitModel.secondOrderHarmonicPerturbation.crc = crc;
+ keplerianOrbitModel.secondOrderHarmonicPerturbation.cuc = cuc;
+ keplerianOrbitModel.secondOrderHarmonicPerturbation.cus = cus;
+ env->DeleteLocalRef(secondOrderHarmonicPerturbationObj);
+}
+
+void GnssAssistanceUtil::setKlobucharIonosphericModel(
+ JNIEnv* env, jobject klobucharIonosphericModelObj,
+ KlobucharIonosphericModel& klobucharIonosphericModel) {
+ if (klobucharIonosphericModelObj == nullptr) return;
+ jdouble alpha0 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetAlpha0);
+ jdouble alpha1 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetAlpha1);
+ jdouble alpha2 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetAlpha2);
+ jdouble alpha3 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetAlpha3);
+ jdouble beta0 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetBeta0);
+ jdouble beta1 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetBeta1);
+ jdouble beta2 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetBeta2);
+ jdouble beta3 = env->CallDoubleMethod(klobucharIonosphericModelObj,
+ method_klobucharIonosphericModelGetBeta3);
+ klobucharIonosphericModel.alpha0 = alpha0;
+ klobucharIonosphericModel.alpha1 = alpha1;
+ klobucharIonosphericModel.alpha2 = alpha2;
+ klobucharIonosphericModel.alpha3 = alpha3;
+ klobucharIonosphericModel.beta0 = beta0;
+ klobucharIonosphericModel.beta1 = beta1;
+ klobucharIonosphericModel.beta2 = beta2;
+ klobucharIonosphericModel.beta3 = beta3;
+}
+
+void GnssAssistanceUtil::setUtcModel(JNIEnv* env, jobject utcModelObj, UtcModel& utcModel) {
+ if (utcModelObj == nullptr) {
+ utcModel.weekNumber = -1;
+ return;
+ }
+ jdouble a0 = env->CallDoubleMethod(utcModelObj, method_utcModelGetA0);
+ jdouble a1 = env->CallDoubleMethod(utcModelObj, method_utcModelGetA1);
+ jlong timeOfWeek = env->CallLongMethod(utcModelObj, method_utcModelGetTimeOfWeek);
+ jint weekNumber = env->CallIntMethod(utcModelObj, method_utcModelGetWeekNumber);
+ utcModel.a0 = a0;
+ utcModel.a1 = a1;
+ utcModel.timeOfWeek = timeOfWeek;
+ utcModel.weekNumber = static_cast<int32_t>(weekNumber);
+}
+
+void GnssAssistanceUtil::setGnssAlmanac(JNIEnv* env, jobject gnssAlmanacObj,
+ GnssAlmanac& gnssAlmanac) {
+ if (gnssAlmanacObj == nullptr) {
+ gnssAlmanac.weekNumber = -1;
+ return;
+ }
+ jlong issueDateMillis =
+ env->CallLongMethod(gnssAlmanacObj, method_gnssAlmanacGetIssueDateMillis);
+ jint ioda = env->CallIntMethod(gnssAlmanacObj, method_gnssAlmanacGetIoda);
+ jint weekNumber = env->CallIntMethod(gnssAlmanacObj, method_gnssAlmanacGetWeekNumber);
+ jlong toaSeconds = env->CallLongMethod(gnssAlmanacObj, method_gnssAlmanacGetToaSeconds);
+ jboolean isCompleteAlmanacProvided =
+ env->CallBooleanMethod(gnssAlmanacObj, method_gnssAlmanacIsCompleteAlmanacProvided);
+ gnssAlmanac.issueDateMs = issueDateMillis;
+ gnssAlmanac.ioda = ioda;
+ gnssAlmanac.weekNumber = weekNumber;
+ gnssAlmanac.toaSeconds = toaSeconds;
+ gnssAlmanac.isCompleteAlmanacProvided = isCompleteAlmanacProvided;
+
+ jobject satelliteAlmanacsListObj =
+ env->CallObjectMethod(gnssAlmanacObj, method_gnssAlmanacGetSatelliteAlmanacs);
+ auto len = env->CallIntMethod(satelliteAlmanacsListObj, method_listSize);
+ std::vector<GnssSatelliteAlmanac> list(len);
+ for (uint16_t i = 0; i < len; ++i) {
+ jobject gnssSatelliteAlmanacObj =
+ env->CallObjectMethod(satelliteAlmanacsListObj, method_listGet, i);
+ if (gnssSatelliteAlmanacObj == nullptr) continue;
+ GnssSatelliteAlmanac gnssSatelliteAlmanac;
+ jint svid = env->CallIntMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetSvid);
+ jint svHealth =
+ env->CallIntMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetSvHealth);
+ jdouble af0 = env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetAf0);
+ jdouble af1 = env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetAf1);
+ jdouble eccentricity = env->CallDoubleMethod(gnssSatelliteAlmanacObj,
+ method_satelliteAlmanacGetEccentricity);
+ jdouble inclination = env->CallDoubleMethod(gnssSatelliteAlmanacObj,
+ method_satelliteAlmanacGetInclination);
+ jdouble m0 = env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetM0);
+ jdouble omega =
+ env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetOmega);
+ jdouble omega0 =
+ env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetOmega0);
+ jdouble omegaDot =
+ env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetOmegaDot);
+ jdouble rootA =
+ env->CallDoubleMethod(gnssSatelliteAlmanacObj, method_satelliteAlmanacGetRootA);
+ gnssSatelliteAlmanac.svid = static_cast<int32_t>(svid);
+ gnssSatelliteAlmanac.svHealth = static_cast<int32_t>(svHealth);
+ gnssSatelliteAlmanac.af0 = af0;
+ gnssSatelliteAlmanac.af1 = af1;
+ gnssSatelliteAlmanac.eccentricity = eccentricity;
+ gnssSatelliteAlmanac.inclination = inclination;
+ gnssSatelliteAlmanac.m0 = m0;
+ gnssSatelliteAlmanac.omega = omega;
+ gnssSatelliteAlmanac.omega0 = omega0;
+ gnssSatelliteAlmanac.omegaDot = omegaDot;
+ gnssSatelliteAlmanac.rootA = rootA;
+ list.at(i) = gnssSatelliteAlmanac;
+ env->DeleteLocalRef(gnssSatelliteAlmanacObj);
+ }
+ gnssAlmanac.satelliteAlmanacs = list;
+ env->DeleteLocalRef(satelliteAlmanacsListObj);
+}
+
+void GnssAssistanceUtil::setAuxiliaryInformation(JNIEnv* env, jobject auxiliaryInformationObj,
+ AuxiliaryInformation& auxiliaryInformation) {
+ if (auxiliaryInformationObj == nullptr) {
+ auxiliaryInformation.svid = -1;
+ return;
+ }
+ jint svid = env->CallIntMethod(auxiliaryInformationObj, method_auxiliaryInformationGetSvid);
+ jobject availableSignalTypesObj =
+ env->CallObjectMethod(auxiliaryInformationObj,
+ method_auxiliaryInformationGetAvailableSignalTypes);
+ auto size = env->CallIntMethod(availableSignalTypesObj, method_listSize);
+ std::vector<GnssSignalType> availableSignalTypes(size);
+ for (uint16_t i = 0; i < size; ++i) {
+ jobject availableSignalTypeObj =
+ env->CallObjectMethod(availableSignalTypesObj, method_listGet, i);
+ GnssSignalType availableSignalType;
+ setGnssSignalType(env, availableSignalTypeObj, availableSignalType);
+ availableSignalTypes.at(i) = availableSignalType;
+ env->DeleteLocalRef(availableSignalTypeObj);
+ }
+ jint frequencyChannelNumber =
+ env->CallIntMethod(auxiliaryInformationObj,
+ method_auxiliaryInformationGetFrequencyChannelNumber);
+ jint satType =
+ env->CallIntMethod(auxiliaryInformationObj, method_auxiliaryInformationGetSatType);
+ auxiliaryInformation.svid = static_cast<int32_t>(svid);
+ auxiliaryInformation.availableSignalTypes = availableSignalTypes;
+ auxiliaryInformation.frequencyChannelNumber = static_cast<int32_t>(frequencyChannelNumber);
+ auxiliaryInformation.satType = static_cast<BeidouB1CSatelliteOrbitType>(satType);
+ env->DeleteLocalRef(availableSignalTypesObj);
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssAssistance.h b/services/core/jni/gnss/GnssAssistance.h
new file mode 100644
index 000000000000..ee97e19371f8
--- /dev/null
+++ b/services/core/jni/gnss/GnssAssistance.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSSASSITANCE_H
+#define _ANDROID_SERVER_GNSSASSITANCE_H
+
+#include <sys/stat.h>
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/gnss_assistance/BnGnssAssistanceInterface.h>
+#include <log/log.h>
+
+#include "GnssAssistanceCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+using IGnssAssistanceInterface = android::hardware::gnss::gnss_assistance::IGnssAssistanceInterface;
+using IGnssAssistanceCallback = android::hardware::gnss::gnss_assistance::IGnssAssistanceCallback;
+using BeidouAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance::BeidouAssistance;
+using BeidouSatelliteEphemeris = android::hardware::gnss::gnss_assistance::BeidouSatelliteEphemeris;
+using GalileoAssistance =
+ android::hardware::gnss::gnss_assistance::GnssAssistance::GalileoAssistance;
+using GalileoSatelliteEphemeris =
+ android::hardware::gnss::gnss_assistance::GalileoSatelliteEphemeris;
+using GalileoIonosphericModel = android::hardware::gnss::gnss_assistance::GalileoIonosphericModel;
+using GlonassAssistance =
+ android::hardware::gnss::gnss_assistance::GnssAssistance::GlonassAssistance;
+using GlonassAlmanac = android::hardware::gnss::gnss_assistance::GlonassAlmanac;
+using GlonassSatelliteEphemeris =
+ android::hardware::gnss::gnss_assistance::GlonassSatelliteEphemeris;
+using GnssAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance;
+using GnssSignalType = android::hardware::gnss::GnssSignalType;
+using GpsAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance::GpsAssistance;
+using QzssAssistance = android::hardware::gnss::gnss_assistance::GnssAssistance::QzssAssistance;
+using GnssAlmanac = android::hardware::gnss::gnss_assistance::GnssAlmanac;
+using GnssSatelliteCorrections =
+ android::hardware::gnss::gnss_assistance::GnssAssistance::GnssSatelliteCorrections;
+using GpsSatelliteEphemeris = android::hardware::gnss::gnss_assistance::GpsSatelliteEphemeris;
+using SatelliteEphemerisTime = android::hardware::gnss::gnss_assistance::SatelliteEphemerisTime;
+using UtcModel = android::hardware::gnss::gnss_assistance::UtcModel;
+using LeapSecondsModel = android::hardware::gnss::gnss_assistance::LeapSecondsModel;
+using KeplerianOrbitModel = android::hardware::gnss::gnss_assistance::KeplerianOrbitModel;
+using KlobucharIonosphericModel =
+ android::hardware::gnss::gnss_assistance::KlobucharIonosphericModel;
+using TimeModel = android::hardware::gnss::gnss_assistance::TimeModel;
+using RealTimeIntegrityModel = android::hardware::gnss::gnss_assistance::RealTimeIntegrityModel;
+using AuxiliaryInformation = android::hardware::gnss::gnss_assistance::AuxiliaryInformation;
+
+void GnssAssistance_class_init_once(JNIEnv* env, jclass clazz);
+
+class GnssAssistanceInterface {
+public:
+ GnssAssistanceInterface(const sp<IGnssAssistanceInterface>& iGnssAssistance);
+ jboolean injectGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj);
+ jboolean setCallback(const sp<IGnssAssistanceCallback>& callback);
+
+private:
+ const sp<IGnssAssistanceInterface> mGnssAssistanceInterface;
+};
+
+struct GnssAssistanceUtil {
+ static void setGlonassAssistance(JNIEnv* env, jobject glonassAssistanceObj,
+ GlonassAssistance& galileoAssistance);
+ static void setGlonassSatelliteEphemeris(
+ JNIEnv* env, jobject glonassSatelliteEphemerisObj,
+ std::vector<GlonassSatelliteEphemeris>& glonassSatelliteEphemerisList);
+ static void setGlonassAlmanac(JNIEnv* env, jobject glonassAlmanacObj,
+ GlonassAlmanac& glonassAlmanac);
+ static void setBeidouAssistance(JNIEnv* env, jobject beidouAssistanceObj,
+ BeidouAssistance& beidouAssistance);
+ static void setBeidouSatelliteEphemeris(
+ JNIEnv* env, jobject beidouSatelliteEphemerisObj,
+ std::vector<BeidouSatelliteEphemeris>& beidouSatelliteEphemerisList);
+ static void setGalileoAssistance(JNIEnv* env, jobject galileoAssistanceObj,
+ GalileoAssistance& galileoAssistance);
+ static void setGalileoSatelliteEphemeris(
+ JNIEnv* env, jobject galileoSatelliteEphemerisObj,
+ std::vector<GalileoSatelliteEphemeris>& galileoSatelliteEphemerisList);
+ static void setGaliloKlobucharIonosphericModel(JNIEnv* env, jobject galileoIonosphericModelObj,
+ GalileoIonosphericModel& ionosphericModel);
+ static void setGnssAssistance(JNIEnv* env, jobject gnssAssistanceObj,
+ GnssAssistance& gnssAssistance);
+ static void setGpsAssistance(JNIEnv* env, jobject gpsAssistanceObj,
+ GpsAssistance& gpsAssistance);
+ template <class T>
+ static void setGpsOrQzssSatelliteEphemeris(JNIEnv* env, jobject satelliteEphemerisObj,
+ std::vector<T>& satelliteEphemeris);
+ static void setQzssAssistance(JNIEnv* env, jobject qzssAssistanceObj,
+ QzssAssistance& qzssAssistance);
+ static void setGnssAlmanac(JNIEnv* env, jobject gnssAlmanacObj, GnssAlmanac& gnssAlmanac);
+ static void setGnssSignalType(JNIEnv* env, jobject gnssSignalTypeObj,
+ GnssSignalType& gnssSignalType);
+ static void setKeplerianOrbitModel(JNIEnv* env, jobject keplerianOrbitModelObj,
+ KeplerianOrbitModel& keplerianOrbitModel);
+ static void setKlobucharIonosphericModel(JNIEnv* env, jobject klobucharIonosphericModelObj,
+ KlobucharIonosphericModel& klobucharIonosphericModel);
+ static void setTimeModels(JNIEnv* env, jobject timeModelsObj,
+ std::vector<TimeModel>& timeModels);
+ static void setLeapSecondsModel(JNIEnv* env, jobject leapSecondsModelObj,
+ LeapSecondsModel& leapSecondsModel);
+ static void setRealTimeIntegrityModels(
+ JNIEnv* env, jobject realTimeIntegrityModelsObj,
+ std::vector<RealTimeIntegrityModel>& realTimeIntegrityModels);
+
+ static void setSatelliteEphemerisTime(JNIEnv* env, jobject satelliteEphemerisTimeObj,
+ SatelliteEphemerisTime& satelliteEphemerisTime);
+ static void setUtcModel(JNIEnv* env, jobject utcModelObj, UtcModel& utcModel);
+ static void setSatelliteCorrections(
+ JNIEnv* env, jobject satelliteCorrectionsObj,
+ std::vector<GnssSatelliteCorrections>& satelliteCorrections);
+ static void setAuxiliaryInformation(JNIEnv* env, jobject auxiliaryInformationObj,
+ AuxiliaryInformation& auxiliaryInformation);
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSSASSITANCE__H
diff --git a/services/core/jni/gnss/GnssAssistanceCallback.cpp b/services/core/jni/gnss/GnssAssistanceCallback.cpp
new file mode 100644
index 000000000000..dbb27d72663e
--- /dev/null
+++ b/services/core/jni/gnss/GnssAssistanceCallback.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GnssAssistanceCbJni"
+
+#include "GnssAssistanceCallback.h"
+
+#include "Utils.h"
+
+namespace {
+jmethodID method_gnssAssistanceInjectRequest;
+} // anonymous namespace
+
+namespace android::gnss {
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+
+void GnssAssistanceCallback_class_init_once(JNIEnv* env, jclass clazz) {
+ method_gnssAssistanceInjectRequest =
+ env->GetStaticMethodID(clazz, "gnssAssistanceInjectRequest", "()V");
+}
+
+// Implementation of android::hardware::gnss::gnss_assistance::GnssAssistanceCallback.
+
+Status GnssAssistanceCallback::injectRequestCb() {
+ ALOGD("%s.", __func__);
+ JNIEnv* env = getJniEnv();
+ env->CallVoidMethod(mCallbacksObj, method_gnssAssistanceInjectRequest);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return Status::ok();
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssAssistanceCallback.h b/services/core/jni/gnss/GnssAssistanceCallback.h
new file mode 100644
index 000000000000..4c8c5fc06730
--- /dev/null
+++ b/services/core/jni/gnss/GnssAssistanceCallback.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSASSITANCECALLBACK_H
+#define _ANDROID_SERVER_GNSS_GNSSASSITANCECALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/gnss_assistance/BnGnssAssistanceCallback.h>
+#include <log/log.h>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+void GnssAssistanceCallback_class_init_once(JNIEnv* env, jclass clazz);
+
+/*
+ * GnssAssistanceCallback class implements the callback methods required by the
+ * android::hardware::gnss::gnss_assistance::IGnssAssistanceCallback interface.
+ */
+class GnssAssistanceCallback : public hardware::gnss::gnss_assistance::BnGnssAssistanceCallback {
+public:
+ GnssAssistanceCallback() {}
+ binder::Status injectRequestCb() override;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSASSITANCECALLBACK_H
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index a81a0b3251c8..4b98a723fc2d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -50,6 +50,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.PrintWriterPrinter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
@@ -1484,5 +1485,12 @@ class ActiveAdmin {
pw.print("mProvisioningContext=");
pw.println(mProvisioningContext);
}
+
+ if (info != null) {
+ pw.println("DeviceAdminInfo:");
+ pw.increaseIndent();
+ info.dump(new PrintWriterPrinter(pw), "");
+ pw.decreaseIndent();
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 215d6ca964eb..51ed6bb2aa40 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -245,6 +245,7 @@ import static android.app.admin.ProvisioningException.ERROR_REMOVE_NON_REQUIRED_
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.content.Context.RECEIVER_NOT_EXPORTED;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -486,6 +487,7 @@ import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
+import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
@@ -643,6 +645,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
static final String ACTION_PROFILE_OFF_DEADLINE =
"com.android.server.ACTION_PROFILE_OFF_DEADLINE";
+ /** Broadcast action invoked when a managed eSIM is removed while deleting work profile. */
+ private static final String ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE =
+ "com.android.server.ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE";
+
+ /** Extra for the subscription ID of the managed eSIM removed while deleting work profile. */
+ private static final String EXTRA_REMOVED_ESIM_SUBSCRIPTION_ID =
+ "com.android.server.EXTRA_ESIM_REMOVED_WITH_MANAGED_PROFILE_SUBSCRIPTION_ID";
+
private static final String CALLED_FROM_PARENT = "calledFromParent";
private static final String NOT_CALLED_FROM_PARENT = "notCalledFromParent";
@@ -1266,6 +1276,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
removeCredentialManagementApp(intent.getData().getSchemeSpecificPart());
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
clearWipeProfileNotification();
+ } else if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ removeManagedEmbeddedSubscriptionsForUser(userHandle);
} else if (Intent.ACTION_DATE_CHANGED.equals(action)
|| Intent.ACTION_TIME_CHANGED.equals(action)) {
// Update freeze period record when clock naturally progresses to the next day
@@ -1298,6 +1310,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
triggerPolicyComplianceCheckIfNeeded(userHandle, suspended);
} else if (LOGIN_ACCOUNTS_CHANGED_ACTION.equals(action)) {
calculateHasIncompatibleAccounts();
+ } else if (ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE.equals(action)) {
+ int removedSubscriptionId = intent.getIntExtra(EXTRA_REMOVED_ESIM_SUBSCRIPTION_ID,
+ -1);
+ Slogf.i(LOG_TAG,
+ "Deleted subscription with ID %d because owning managed profile was "
+ + "removed",
+ removedSubscriptionId);
}
}
@@ -2219,9 +2238,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_DATE_CHANGED);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
+ filter = new IntentFilter();
+ filter.addAction(ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null,
+ mHandler, RECEIVER_NOT_EXPORTED);
LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
@@ -3782,9 +3806,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Update user switcher message to activity manager.
ActivityManagerInternal activityManagerInternal =
mInjector.getActivityManagerInternal();
- activityManagerInternal.setSwitchingFromSystemUserMessage(
+ int deviceOwnerUserId = UserHandle.getUserId(deviceOwner.getUid());
+ activityManagerInternal.setSwitchingFromUserMessage(deviceOwnerUserId,
deviceOwner.startUserSessionMessage);
- activityManagerInternal.setSwitchingToSystemUserMessage(
+ activityManagerInternal.setSwitchingToUserMessage(deviceOwnerUserId,
deviceOwner.endUserSessionMessage);
}
@@ -3970,6 +3995,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
deletedUsers.remove(userInfo.id);
}
for (Integer userId : deletedUsers) {
+ removeManagedEmbeddedSubscriptionsForUser(userId);
removeUserData(userId);
mDevicePolicyEngine.handleUserRemoved(userId);
}
@@ -8099,6 +8125,45 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PROFILE_WIPED);
}
+ /**
+ * Remove eSIM subscriptions that are managed by any of the admin packages of the given
+ * userHandle.
+ */
+ private void removeManagedEmbeddedSubscriptionsForUser(int userHandle) {
+ if (!Flags.removeManagedEsimOnWorkProfileDeletion()) {
+ return;
+ }
+
+ Slogf.i(LOG_TAG,
+ "Managed profile with ID=%d deleted: going to remove managed embedded "
+ + "subscriptions", userHandle);
+ String profileOwnerPackage = mOwners.getProfileOwnerPackage(userHandle);
+ if (profileOwnerPackage == null) {
+ Slogf.wtf(LOG_TAG, "Profile owner package for managed profile is null");
+ return;
+ }
+ IntArray managedSubscriptionIds = getSubscriptionIdsInternal(profileOwnerPackage);
+ deleteEmbeddedSubscriptions(managedSubscriptionIds);
+ }
+
+ private void deleteEmbeddedSubscriptions(IntArray subscriptionIds) {
+ EuiccManager euiccManager = mContext.getSystemService(EuiccManager.class);
+ for (int subscriptionId : subscriptionIds.toArray()) {
+ Slogf.i(LOG_TAG, "Deleting embedded subscription with ID %d", subscriptionId);
+ euiccManager.deleteSubscription(subscriptionId,
+ createCallbackPendingIntentForRemovingManagedSubscription(
+ subscriptionId));
+ }
+ }
+
+ private PendingIntent createCallbackPendingIntentForRemovingManagedSubscription(
+ Integer subscriptionId) {
+ Intent intent = new Intent(ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE);
+ intent.putExtra(EXTRA_REMOVED_ESIM_SUBSCRIPTION_ID, subscriptionId);
+ return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+
@Override
public void setFactoryResetProtectionPolicy(ComponentName who, String callerPackageName,
@Nullable FactoryResetProtectionPolicy policy) {
@@ -19652,7 +19717,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mInjector.getActivityManagerInternal()
- .setSwitchingFromSystemUserMessage(startUserSessionMessageString);
+ .setSwitchingFromUserMessage(caller.getUserId(), startUserSessionMessageString);
}
@Override
@@ -19677,7 +19742,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mInjector.getActivityManagerInternal()
- .setSwitchingToSystemUserMessage(endUserSessionMessageString);
+ .setSwitchingToUserMessage(caller.getUserId(), endUserSessionMessageString);
}
@Override
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index 8e749529978e..69e856de401a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -20,6 +20,7 @@ import static android.hardware.SensorManager.SENSOR_DELAY_FASTEST;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.TYPE_EXTERNAL;
@@ -324,6 +325,12 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
}
if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
+ if (mLastHingeAngleSensorEvent != null
+ && Trace.isTagEnabled(TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.instant(TRACE_TAG_SYSTEM_SERVER,
+ "[Device state changed] Last hinge sensor event timestamp: "
+ + mLastHingeAngleSensorEvent.timestamp);
+ }
mLastReportedState = newState;
stateToReport = newState;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 860b6fb1dcd1..788b3b883160 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -366,6 +366,8 @@ public final class SystemServer implements Dumpable {
"com.android.clockwork.time.WearTimeService";
private static final String WEAR_SETTINGS_SERVICE_CLASS =
"com.android.clockwork.settings.WearSettingsService";
+ private static final String WEAR_GESTURE_SERVICE_CLASS =
+ "com.android.clockwork.gesture.WearGestureService";
private static final String WRIST_ORIENTATION_SERVICE_CLASS =
"com.android.clockwork.wristorientation.WristOrientationService";
private static final String IOT_SERVICE_CLASS =
@@ -2844,6 +2846,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS);
t.traceEnd();
}
+
+ if (android.server.Flags.wearGestureApi()
+ && SystemProperties.getBoolean("config.enable_gesture_api", false)) {
+ t.traceBegin("StartWearGestureService");
+ mSystemServiceManager.startService(WEAR_GESTURE_SERVICE_CLASS);
+ t.traceEnd();
+ }
}
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 86ccd878de7c..7a6bd75e5893 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -65,4 +65,12 @@ flag {
namespace: "package_manager_service"
description: "Remove AppIntegrityManagerService"
bug: "364200023"
+}
+
+flag {
+ name: "wear_gesture_api"
+ namespace: "wear_frameworks"
+ description: "Whether the Wear Gesture API is available."
+ bug: "396154116"
+ is_exported: true
} \ No newline at end of file
diff --git a/services/proguard.flags b/services/proguard.flags
index 8d8b418ced0b..dd3757c9e360 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -59,6 +59,7 @@
# Referenced in wear-service
-keep public class com.android.server.wm.WindowManagerInternal { *; }
+-keep public class com.android.server.wm.WindowManagerInternal$WindowFocusChangeListener { *; }
# JNI keep rules
# The global keep rule for native methods allows stripping of such methods if they're unreferenced
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 2dd16f68dc56..80a3a8788d80 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -109,6 +109,7 @@ import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.backup.BackupAgentConnectionManager;
import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.BackupRestoreTask.CancellationReason;
import com.android.server.backup.BackupWakeLock;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.KeyValueBackupJob;
@@ -2412,7 +2413,7 @@ public class KeyValueBackupTaskTest {
KeyValueBackupTask task = spy(createKeyValueBackupTask(transportMock, PACKAGE_1));
doNothing().when(task).waitCancel();
- task.handleCancel(true);
+ task.handleCancel(CancellationReason.EXTERNAL);
InOrder inOrder = inOrder(task);
inOrder.verify(task).markCancel();
@@ -2420,12 +2421,14 @@ public class KeyValueBackupTaskTest {
}
@Test
- public void testHandleCancel_whenCancelAllFalse_throws() throws Exception {
+ public void testHandleCancel_timeout_throws() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgentWithData(PACKAGE_1);
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
- expectThrows(IllegalArgumentException.class, () -> task.handleCancel(false));
+ expectThrows(
+ IllegalArgumentException.class,
+ () -> task.handleCancel(CancellationReason.TIMEOUT));
}
/** Do not update backup token if no data was moved. */
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
index 2c2e5fdb68d9..b354e36e1e8f 100644
--- a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
@@ -29,6 +29,7 @@ android_test {
static_libs: [
"androidx.test.core",
"androidx.test.runner",
+ "flag-junit",
"hamcrest-library",
"platform-test-annotations",
"services.core",
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS b/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS
new file mode 100644
index 000000000000..2522426d93f8
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/UprobeStats:/OWNERS \ No newline at end of file
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
index 085f5953f0e5..d3a287dc6423 100644
--- a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
@@ -17,6 +17,9 @@
package com.android.server;
public class TestClass {
+ TestClass() {
+ }
+
void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g,
short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) {
}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
index 6e14bad11837..905c244af4ec 100644
--- a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
@@ -22,6 +22,9 @@ import static org.junit.Assert.assertThrows;
import android.os.instrumentation.MethodDescriptor;
import android.os.instrumentation.MethodDescriptorParser;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.filters.SmallTest;
@@ -31,9 +34,10 @@ import com.android.server.TestClass;
import com.android.server.TestInterface;
import com.android.server.TestInterfaceImpl;
+import org.junit.Rule;
import org.junit.Test;
-import java.lang.reflect.Method;
+import java.lang.reflect.Executable;
/**
@@ -53,6 +57,10 @@ public class ParseMethodDescriptorTest {
private static final String[] CLASS_PARAMS =
new String[]{"java.lang.String", "java.lang.String[]"};
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Test
public void primitiveParams() {
assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
@@ -66,6 +74,13 @@ public class ParseMethodDescriptorTest {
}
@Test
+ @RequiresFlagsEnabled(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS_V2)
+ public void constructor() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "<init>"));
+ }
+
+ @Test
public void publicMethod() {
assertNotNull(
parseMethodDescriptor(TestClass.class.getName(), "publicMethod"));
@@ -119,13 +134,14 @@ public class ParseMethodDescriptorTest {
new String[]{"int"}));
}
- private Method parseMethodDescriptor(String fqcn, String methodName) {
+ private Executable parseMethodDescriptor(String fqcn, String methodName) {
return MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(),
getMethodDescriptor(fqcn, methodName, new String[]{}));
}
- private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) {
+ private Executable parseMethodDescriptor(
+ String fqcn, String methodName, String[] fqParameters) {
return MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(),
getMethodDescriptor(fqcn, methodName, fqParameters));
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index ae9a34efc222..c1d8382fcd0e 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -73,10 +73,7 @@ android_ravenwood_test {
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
- "framework",
- "ravenwood-runtime",
- "ravenwood-utils",
- "services",
+ "services.core",
],
libs: [
"android.test.base.stubs.system",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index d6a685378d9e..ea01fc4e6140 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -18,9 +18,6 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
- <!-- Needed for reading the app files for the test artifacts -->
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
@@ -44,7 +41,7 @@
<!-- Collect output of DumpOnFailure -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/data/user/0/com.android.apps.inputmethod.simpleime/files" />
+ <option name="directory-keys" value="/sdcard/DumpOnFailure" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 2cd860ae6c12..e263e85f020f 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -77,6 +77,8 @@ import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -1294,6 +1296,14 @@ public class InputMethodServiceTest {
mInstrumentation.waitForIdleSync();
final var postScreenshot = mInstrumentation.getUiAutomation().takeScreenshot();
mDumpOnFailure.dumpOnFailure("post-getUiObject", postScreenshot);
+ try {
+ final var outputStream = new ByteArrayOutputStream();
+ mUiDevice.dumpWindowHierarchy(outputStream);
+ final String windowHierarchy = outputStream.toString(StandardCharsets.UTF_8);
+ mDumpOnFailure.dumpOnFailure("post-getUiObject", windowHierarchy);
+ } catch (Exception e) {
+ Log.i(TAG, "Failed to dump windowHierarchy", e);
+ }
assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNotNull();
return uiObject;
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 05615f68427d..2339a940e2d0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -287,6 +287,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
mTargetSdkVersion /* unverifiedTargetSdkVersion */,
mUserId /* userId */,
- mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+ mMockImeOnBackInvokedDispatcher /* imeDispatcher */,
+ true /* imeRequestedVisible */);
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 70eeae648dd0..aa779197f301 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -267,7 +267,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes
// visibility state will be preserved to the current window state.
final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(
mWindowToken);
- mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
+ mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */,
+ true /* imeRequestedVisible */);
assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
lastState.isRequestedImeVisible());
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index 11abc9469c82..b81b570389da 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -320,7 +320,8 @@ public class InputMethodManagerServiceWindowGainedFocusTest
mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
mTargetSdkVersion /* unverifiedTargetSdkVersion */,
mUserId /* userId */,
- mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+ mMockImeOnBackInvokedDispatcher /* imeDispatcher */,
+ true /* imeRequestedVisible */);
}
@Test
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index 00873de4aaed..b6965a4341d7 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -20,6 +20,9 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Enable writing output of DumpOnFailure to external storage -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
<application android:debuggable="true"
android:label="@string/app_name">
<service
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index e545a49d3139..554b5b4297f2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -19,8 +19,8 @@ package com.android.server.pm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
index afb18f5be669..5c9ba401bf88 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt
@@ -32,7 +32,7 @@ internal class IgnoreableExpect : TestRule {
private var ignore = false
- override fun apply(base: Statement?, description: Description?): Statement {
+ override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
ignore = false
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index c151732cec66..2770caa8aaa4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -324,7 +324,8 @@ public class DisplayManagerServiceTest {
return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
@Override
- public IBinder createDisplay(String name, boolean secure, String uniqueId,
+ public IBinder createDisplay(String name, boolean secure,
+ boolean optimizeForPower, String uniqueId,
float requestedRefreshRate) {
return mMockDisplayToken;
}
@@ -332,6 +333,10 @@ public class DisplayManagerServiceTest {
@Override
public void destroyDisplay(IBinder displayToken) {
}
+
+ @Override
+ public void setDisplayPowerMode(IBinder displayToken, int mode) {
+ }
}, flags);
}
@@ -3910,6 +3915,7 @@ public class DisplayManagerServiceTest {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
+ DisplayManagerInternal localService = displayManager.new LocalService();
Handler handler = displayManager.getDisplayHandler();
waitForIdleHandler(handler);
@@ -3918,8 +3924,8 @@ public class DisplayManagerServiceTest {
INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
waitForIdleHandler(handler);
- var topology = initDisplayTopology(displayManager, displayManagerBinderService, callback,
- handler, /*shouldEmitTopologyChangeEvent=*/ true);
+ var topology = initDisplayTopology(displayManager, displayManagerBinderService,
+ localService, callback, handler, /*shouldEmitTopologyChangeEvent=*/ true);
callback.clear();
callback.expectsEvent(TOPOLOGY_CHANGED_EVENT);
displayManagerBinderService.setDisplayTopology(topology);
@@ -3934,6 +3940,7 @@ public class DisplayManagerServiceTest {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
+ DisplayManagerInternal localService = displayManager.new LocalService();
Handler handler = displayManager.getDisplayHandler();
waitForIdleHandler(handler);
@@ -3943,8 +3950,8 @@ public class DisplayManagerServiceTest {
STANDARD_DISPLAY_EVENTS);
waitForIdleHandler(handler);
- var topology = initDisplayTopology(displayManager, displayManagerBinderService, callback,
- handler, /*shouldEmitTopologyChangeEvent=*/ false);
+ var topology = initDisplayTopology(displayManager, displayManagerBinderService,
+ localService, callback, handler, /*shouldEmitTopologyChangeEvent=*/ false);
callback.clear();
callback.expectsEvent(TOPOLOGY_CHANGED_EVENT); // should not happen
displayManagerBinderService.setDisplayTopology(topology);
@@ -4702,15 +4709,16 @@ public class DisplayManagerServiceTest {
private DisplayTopology initDisplayTopology(DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
- FakeDisplayManagerCallback callback,
+ DisplayManagerInternal localService, FakeDisplayManagerCallback callback,
Handler handler, boolean shouldEmitTopologyChangeEvent) {
Settings.Global.putInt(mContext.getContentResolver(),
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
callback.expectsEvent(TOPOLOGY_CHANGED_EVENT);
FakeDisplayDevice displayDevice0 =
- createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
int displayId0 = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
displayDevice0);
+ waitForIdleHandler(handler);
if (shouldEmitTopologyChangeEvent) {
callback.waitForExpectedEvent();
} else {
@@ -4724,6 +4732,11 @@ public class DisplayManagerServiceTest {
int displayId1 = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
displayDevice1);
waitForIdleHandler(handler);
+ // Non-default display should not be added until onDisplayBelongToTopologyChanged is called
+ // with true
+ callback.waitForNonExpectedEvent();
+ localService.onDisplayBelongToTopologyChanged(displayId1, true);
+ waitForIdleHandler(handler);
if (shouldEmitTopologyChangeEvent) {
callback.waitForExpectedEvent();
} else {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index 29f07227a12d..fecbc7c81347 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -25,7 +25,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.hardware.Sensor;
@@ -396,7 +396,7 @@ public final class DisplayPowerProximityStateControllerTest {
assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
- verifyZeroInteractions(mWakelockController);
+ verifyNoMoreInteractions(mWakelockController);
}
private void setScreenOffBecauseOfPositiveProximityState() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 3c134b5d5482..6dc7361e5366 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -47,7 +47,7 @@ class DisplayTopologyCoordinatorTest {
private val mockTopology = mock<DisplayTopology>()
private val mockTopologyCopy = mock<DisplayTopology>()
private val mockTopologyGraph = mock<DisplayTopologyGraph>()
- private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
+ private val mockIsExtendedDisplayAllowed = mock<() -> Boolean>()
private val mockTopologySavedCallback = mock<() -> Unit>()
private val mockTopologyChangedCallback =
mock<(android.util.Pair<DisplayTopology, DisplayTopologyGraph>) -> Unit>()
@@ -73,10 +73,10 @@ class DisplayTopologyCoordinatorTest {
) =
mockTopologyStore
}
- whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
+ whenever(mockIsExtendedDisplayAllowed()).thenReturn(true)
whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
whenever(mockTopologyCopy.getGraph(any())).thenReturn(mockTopologyGraph)
- coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
+ coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayAllowed,
mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(),
mockTopologySavedCallback)
}
@@ -195,7 +195,7 @@ class DisplayTopologyCoordinatorTest {
@Test
fun addDisplay_external_extendedDisplaysDisabled() {
- whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
+ whenever(mockIsExtendedDisplayAllowed()).thenReturn(false)
for (displayInfo in displayInfos) {
coordinator.onDisplayAdded(displayInfo)
@@ -208,7 +208,7 @@ class DisplayTopologyCoordinatorTest {
@Test
fun addDisplay_overlay_extendedDisplaysDisabled() {
displayInfos[0].type = Display.TYPE_OVERLAY
- whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
+ whenever(mockIsExtendedDisplayAllowed()).thenReturn(false)
for (displayInfo in displayInfos) {
coordinator.onDisplayAdded(displayInfo)
@@ -220,17 +220,6 @@ class DisplayTopologyCoordinatorTest {
}
@Test
- fun addDisplay_notInDefaultDisplayGroup() {
- displayInfos[0].displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
-
- coordinator.onDisplayAdded(displayInfos[0])
-
- verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
- verify(mockTopologyChangedCallback, never()).invoke(any())
- verify(mockTopologyStore, never()).restoreTopology(any())
- }
-
- @Test
fun updateDisplay() {
whenever(mockTopology.updateDisplay(eq(displayInfos[0].displayId), anyFloat(), anyFloat()))
.thenReturn(true)
@@ -325,7 +314,7 @@ class DisplayTopologyCoordinatorTest {
@Test
fun updateDisplay_external_extendedDisplaysDisabled() {
- whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
+ whenever(mockIsExtendedDisplayAllowed()).thenReturn(false)
for (displayInfo in displayInfos) {
coordinator.onDisplayChanged(displayInfo)
@@ -339,7 +328,7 @@ class DisplayTopologyCoordinatorTest {
@Test
fun updateDisplay_overlay_extendedDisplaysDisabled() {
displayInfos[0].type = Display.TYPE_OVERLAY
- whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
+ whenever(mockIsExtendedDisplayAllowed()).thenReturn(false)
coordinator.onDisplayChanged(displayInfos[0])
@@ -349,17 +338,6 @@ class DisplayTopologyCoordinatorTest {
}
@Test
- fun updateDisplay_notInDefaultDisplayGroup() {
- displayInfos[0].displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
-
- coordinator.onDisplayChanged(displayInfos[0])
-
- verify(mockTopology, never()).updateDisplay(anyInt(), anyFloat(), anyFloat())
- verify(mockTopologyCopy, never()).getGraph(any())
- verify(mockTopologyChangedCallback, never()).invoke(any())
- }
-
- @Test
fun removeDisplay() {
addDisplay()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f8b4113a3c04..b8a5f341eb8a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -234,6 +234,7 @@ public class LocalDisplayAdapterTest {
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
doReturn(true).when(mFlags).isEvenDimmerEnabled();
+ doReturn(true).when(mFlags).isDisplayContentModeManagementEnabled();
initDisplayOffloadSession();
}
@@ -1468,6 +1469,103 @@ public class LocalDisplayAdapterTest {
assertFalse(mDisplayOffloadSession.isActive());
}
+ @Test
+ public void testAllowsContentSwitch_firstDisplay() throws Exception {
+ // Set up a first display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // The first display should be allowed to use the content mode switch
+ DisplayDevice firstDisplayDevice = mListener.addedDisplays.get(0);
+ assertTrue((firstDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_secondaryDisplayPublicAndNotShouldShowOwnContent()
+ throws Exception {
+ // Set up a first display and a secondary display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+
+ // Set the secondary display to be a public display
+ doReturn(new int[0]).when(mMockedResources)
+ .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+ // Disable FLAG_OWN_CONTENT_ONLY for the secondary display
+ doReturn(true).when(mMockedResources)
+ .getBoolean(com.android.internal.R.bool.config_localDisplaysMirrorContent);
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // This secondary display should be allowed to use the content mode switch
+ DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1);
+ assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_privateDisplay() throws Exception {
+ // Set up a first display and a secondary display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+
+ // Set the secondary display to be a private display
+ doReturn(new int[]{ PORT_B }).when(mMockedResources)
+ .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // The private display should not be allowed to use the content mode switch
+ DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1);
+ assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_ownContentOnlyDisplay() throws Exception {
+ // Set up a first display and a secondary display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+
+ // Enable FLAG_OWN_CONTENT_ONLY for the secondary display
+ doReturn(false).when(mMockedResources)
+ .getBoolean(com.android.internal.R.bool.config_localDisplaysMirrorContent);
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // The secondary display with FLAG_OWN_CONTENT_ONLY enabled should not be allowed to use the
+ // content mode switch
+ DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1);
+ assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_flagShouldShowSystemDecorations() throws Exception {
+ // Set up a display
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // Display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled should not be allowed to use the
+ // content mode switch
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+ int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
+ boolean allowsContentModeSwitch =
+ ((flags & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0);
+ boolean shouldShowSystemDecorations =
+ ((flags & DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0);
+ assertFalse(allowsContentModeSwitch && shouldShowSystemDecorations);
+ }
+
private void initDisplayOffloadSession() {
when(mDisplayOffloader.startOffload()).thenReturn(true);
when(mDisplayOffloader.allowAutoBrightnessInDoze()).thenReturn(true);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 9287b3004279..10bea7d331cd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -21,19 +21,29 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.display.DisplayManager;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
+import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -67,6 +77,9 @@ public class VirtualDisplayAdapterTest {
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory;
@@ -380,6 +393,112 @@ public class VirtualDisplayAdapterTest {
}
}
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE)
+ @Test
+ public void neverBlankDisplay_alwaysOn() {
+ // A non-public non-mirror display is considered never blank.
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ "uniqueId", /* surface= */ mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ assertThat(info.state).isEqualTo(Display.STATE_ON);
+ assertThat(info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK)
+ .isEqualTo(DisplayDeviceInfo.FLAG_NEVER_BLANK);
+ }
+
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE)
+ @Test
+ public void virtualDisplayStateChange_propagatesToSurfaceControl() throws Exception {
+ final String uniqueId = "uniqueId";
+ final IBinder displayToken = new Binder();
+ when(mMockSufaceControlDisplayFactory.createDisplay(
+ any(), anyBoolean(), anyBoolean(), eq(uniqueId), anyFloat()))
+ .thenReturn(displayToken);
+
+ // The display needs to be public, otherwise it will be considered never blank.
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ uniqueId, /* surface= */ mSurfaceMock, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
+ mVirtualDisplayConfigMock);
+
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ assertThat(info.state).isEqualTo(Display.STATE_UNKNOWN);
+ assertThat(info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK).isEqualTo(0);
+
+ // Any initial state change is processed because the display state is initially UNKNOWN
+ Runnable stateOnRunnable = device.requestDisplayStateLocked(
+ Display.STATE_ON, /* brightnessState= */ 1.0f, /* sdrBrightnessState= */ 1.0f,
+ /* displayOffloadSession= */ null);
+ assertThat(stateOnRunnable).isNotNull();
+ stateOnRunnable.run();
+ verify(mMockSufaceControlDisplayFactory)
+ .setDisplayPowerMode(displayToken, SurfaceControl.POWER_MODE_NORMAL);
+ verify(mMockCallback).onResumed();
+
+ // Requesting the same display state is a no-op
+ Runnable stateOnSecondRunnable = device.requestDisplayStateLocked(
+ Display.STATE_ON, /* brightnessState= */ 1.0f, /* sdrBrightnessState= */ 1.0f,
+ /* displayOffloadSession= */ null);
+ assertThat(stateOnSecondRunnable).isNull();
+
+ // A change to the display state is processed
+ Runnable stateOffRunnable = device.requestDisplayStateLocked(
+ Display.STATE_OFF, /* brightnessState= */ 1.0f, /* sdrBrightnessState= */ 1.0f,
+ /* displayOffloadSession= */ null);
+ assertThat(stateOffRunnable).isNotNull();
+ stateOffRunnable.run();
+ verify(mMockSufaceControlDisplayFactory)
+ .setDisplayPowerMode(displayToken, SurfaceControl.POWER_MODE_OFF);
+ verify(mMockCallback).onPaused();
+ }
+
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE)
+ @Test
+ public void createVirtualDisplayLocked_neverBlank_optimizesForPower() {
+ final String uniqueId = "uniqueId";
+ final IBinder displayToken = new Binder();
+ final String name = "name";
+ when(mVirtualDisplayConfigMock.getName()).thenReturn(name);
+ when(mMockSufaceControlDisplayFactory.createDisplay(
+ any(), anyBoolean(), anyBoolean(), eq(uniqueId), anyFloat()))
+ .thenReturn(displayToken);
+
+ // Use a private display to cause the display to be never blank.
+ mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ uniqueId, /* surface= */ mSurfaceMock, 0, mVirtualDisplayConfigMock);
+
+ verify(mMockSufaceControlDisplayFactory).createDisplay(eq(name), eq(false), eq(true),
+ eq(uniqueId), anyFloat());
+ }
+
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE)
+ @Test
+ public void createVirtualDisplayLocked_blankable_optimizesForPerformance() {
+ final String uniqueId = "uniqueId";
+ final IBinder displayToken = new Binder();
+ final String name = "name";
+ when(mVirtualDisplayConfigMock.getName()).thenReturn(name);
+ when(mMockSufaceControlDisplayFactory.createDisplay(
+ any(), anyBoolean(), anyBoolean(), eq(uniqueId), anyFloat()))
+ .thenReturn(displayToken);
+
+ // Use a public display to cause the display to be blankable
+ mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ uniqueId, /* surface= */ mSurfaceMock, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
+ mVirtualDisplayConfigMock);
+
+ verify(mMockSufaceControlDisplayFactory).createDisplay(eq(name), eq(false), eq(false),
+ eq(uniqueId), anyFloat());
+ }
+
private IVirtualDisplayCallback createCallback() {
return new IVirtualDisplayCallback.Stub() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
index 019b70ef1424..f067fa10f611 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.hardware.display.DisplayManagerInternal;
@@ -210,7 +210,7 @@ public final class WakelockControllerTest {
// Validate one suspend blocker was released
assertFalse(mWakelockController.isProximityPositiveAcquired());
- verifyZeroInteractions(mDisplayPowerCallbacks);
+ verifyNoMoreInteractions(mDisplayPowerCallbacks);
}
@Test
@@ -238,7 +238,7 @@ public final class WakelockControllerTest {
// Validate one suspend blocker was released
assertFalse(mWakelockController.isProximityNegativeAcquired());
- verifyZeroInteractions(mDisplayPowerCallbacks);
+ verifyNoMoreInteractions(mDisplayPowerCallbacks);
}
@Test
@@ -265,7 +265,7 @@ public final class WakelockControllerTest {
// Validate one suspend blocker was released
assertFalse(mWakelockController.isOnStateChangedPending());
- verifyZeroInteractions(mDisplayPowerCallbacks);
+ verifyNoMoreInteractions(mDisplayPowerCallbacks);
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 49de80179683..bf0543939d85 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -422,7 +421,7 @@ public final class DisplayBrightnessControllerTest {
mDisplayBrightnessController.setAutomaticBrightnessController(
automaticBrightnessController);
assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
- verifyZeroInteractions(automaticBrightnessController);
+ verifyNoMoreInteractions(automaticBrightnessController);
verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index 6929690baaf8..d7c047768c1e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -17,7 +17,6 @@ package com.android.server.display.brightness.clamper
import android.os.UserHandle
import android.platform.test.annotations.RequiresFlagsEnabled
-import android.provider.Settings
import android.testing.TestableContext
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.display.DisplayDeviceConfig
@@ -46,8 +45,6 @@ class BrightnessLowLuxModifierTest {
private var mockDisplayDeviceConfig = mock<DisplayDeviceConfig>()
private val LOW_LUX_BRIGHTNESS = 0.1f
- private val TRANSITION_POINT = 0.25f
- private val NORMAL_RANGE_BRIGHTNESS = 0.3f
@Before
fun setUp() {
@@ -73,127 +70,21 @@ class BrightnessLowLuxModifierTest {
whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.15f))
.thenReturn(0.24f)
- // values above transition point (normal range)
- // nits: 10 -> backlight 0.2 -> brightness -> 0.3
- whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 10f))
- .thenReturn(0.2f)
- whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.2f))
- .thenReturn(NORMAL_RANGE_BRIGHTNESS)
-
// min nits when lux of 400
whenever(mockDisplayDeviceConfig.getMinNitsFromLux(/* lux= */ 400f))
.thenReturn(1.0f)
-
- whenever(mockDisplayDeviceConfig.evenDimmerTransitionPoint).thenReturn(TRANSITION_POINT)
-
- testHandler.flush()
- }
-
- @Test
- fun testSettingOffDisablesModifier() {
- // test transition point ensures brightness doesn't drop when setting is off.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID)
- modifier.recalculateLowerBound()
- testHandler.flush()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
- modifier.setAmbientLux(3000f)
-
testHandler.flush()
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
fun testLuxRestrictsBrightnessRange() {
// test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, USER_ID)
- modifier.setAmbientLux(400f)
-
- testHandler.flush()
-
- assertThat(modifier.isActive).isTrue()
- // Test restriction from lux setting
- assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
- assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testUserRestrictsBrightnessRange() {
- // test that user minimum nits setting prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, USER_ID)
- modifier.recalculateLowerBound()
- testHandler.flush()
-
- // Test restriction from user setting
- assertThat(modifier.isActive).isTrue()
- assertThat(modifier.brightnessReason)
- .isEqualTo(BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
- assertThat(modifier.brightnessLowerBound).isEqualTo(NORMAL_RANGE_BRIGHTNESS)
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testOnToOff() {
- // test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
- modifier.setAmbientLux(400f)
-
- testHandler.flush()
-
- assertThat(modifier.isActive).isTrue()
- // Test restriction from lux setting
- assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
- assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
-
- modifier.recalculateLowerBound()
- testHandler.flush()
-
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testOffToOn() {
- // test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
modifier.setAmbientLux(400f)
testHandler.flush()
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
-
-
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- modifier.recalculateLowerBound()
- testHandler.flush()
-
assertThat(modifier.isActive).isTrue()
// Test restriction from lux setting
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
@@ -204,11 +95,6 @@ class BrightnessLowLuxModifierTest {
@RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
fun testEnabledEvenWhenAutobrightnessIsOff() {
// test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
-
modifier.setAmbientLux(400f)
testHandler.flush()
@@ -225,37 +111,5 @@ class BrightnessLowLuxModifierTest {
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
}
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testUserSwitch() {
- // nits: 0.5 -> backlight 0.01 -> brightness -> 0.05
- whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 0.5f))
- .thenReturn(0.01f)
- whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.01f))
- .thenReturn(0.05f)
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
-
- modifier.recalculateLowerBound()
-
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - i.e. off
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.5f, USER_ID)
- modifier.onSwitchUser()
-
- assertThat(modifier.isActive).isTrue()
- assertThat(modifier.brightnessReason).isEqualTo(
- BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
- assertThat(modifier.brightnessLowerBound).isEqualTo(0.05f)
- }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index 22c10f967398..067230a66228 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -29,7 +29,6 @@ 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.annotation.NonNull;
import android.app.ActivityManager;
@@ -113,10 +112,10 @@ public class ColorDisplayServiceTest {
doReturn(mContext).when(mContext).getApplicationContext();
final Resources res = Mockito.spy(mContext.getResources());
+ doReturn(res).when(mContext).getResources();
doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
- when(mContext.getResources()).thenReturn(res);
mResourcesSpy = res;
mUserId = ActivityManager.getCurrentUser();
@@ -1105,10 +1104,11 @@ public class ColorDisplayServiceTest {
@Test
public void compositionColorSpaces_noResources() {
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
- .thenReturn(new int[] {});
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
- .thenReturn(new int[] {});
+ doReturn(new int[] {}).when(mResourcesSpy)
+ .getIntArray(R.array.config_displayCompositionColorModes);
+ doReturn(new int[] {}).when(mResourcesSpy)
+ .getIntArray(R.array.config_displayCompositionColorSpaces);
+
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
verify(mDisplayTransformManager).setColorMode(
@@ -1118,16 +1118,15 @@ public class ColorDisplayServiceTest {
@Test
public void compositionColorSpaces_invalidResources() {
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
- .thenReturn(new int[] {
- ColorDisplayManager.COLOR_MODE_NATURAL,
- // Missing second color mode
- });
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
- .thenReturn(new int[] {
- Display.COLOR_MODE_SRGB,
- Display.COLOR_MODE_DISPLAY_P3
- });
+ doReturn(new int[] {
+ ColorDisplayManager.COLOR_MODE_NATURAL,
+ // Missing second color mode
+ }).when(mResourcesSpy).getIntArray(R.array.config_displayCompositionColorModes);
+ doReturn(new int[] {
+ Display.COLOR_MODE_SRGB,
+ Display.COLOR_MODE_DISPLAY_P3
+ }).when(mResourcesSpy).getIntArray(R.array.config_displayCompositionColorSpaces);
+
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
verify(mDisplayTransformManager).setColorMode(
@@ -1137,14 +1136,13 @@ public class ColorDisplayServiceTest {
@Test
public void compositionColorSpaces_validResources_validColorMode() {
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
- .thenReturn(new int[] {
- ColorDisplayManager.COLOR_MODE_NATURAL
- });
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
- .thenReturn(new int[] {
- Display.COLOR_MODE_SRGB,
- });
+ doReturn(new int[] {
+ ColorDisplayManager.COLOR_MODE_NATURAL
+ }).when(mResourcesSpy).getIntArray(R.array.config_displayCompositionColorModes);
+ doReturn(new int[] {
+ Display.COLOR_MODE_SRGB,
+ }).when(mResourcesSpy).getIntArray(R.array.config_displayCompositionColorSpaces);
+
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
verify(mDisplayTransformManager).setColorMode(
@@ -1154,14 +1152,13 @@ public class ColorDisplayServiceTest {
@Test
public void compositionColorSpaces_validResources_invalidColorMode() {
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
- .thenReturn(new int[] {
- ColorDisplayManager.COLOR_MODE_NATURAL
- });
- when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
- .thenReturn(new int[] {
- Display.COLOR_MODE_SRGB,
- });
+ doReturn(new int[] {
+ ColorDisplayManager.COLOR_MODE_NATURAL
+ }).when(mResourcesSpy).getIntArray(R.array.config_displayCompositionColorModes);
+ doReturn(new int[] {
+ Display.COLOR_MODE_SRGB,
+ }).when(mResourcesSpy).getIntArray(R.array.config_displayCompositionColorSpaces);
+
setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
startService();
verify(mDisplayTransformManager).setColorMode(
@@ -1171,8 +1168,7 @@ public class ColorDisplayServiceTest {
@Test
public void getColorMode_noAvailableModes_returnsNotSet() {
- when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
- .thenReturn(new int[] {});
+ doReturn(new int[] {}).when(mResourcesSpy).getIntArray(R.array.config_availableColorModes);
startService();
verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), any(), anyInt());
assertThat(mBinderService.getColorMode()).isEqualTo(-1);
@@ -1197,16 +1193,17 @@ public class ColorDisplayServiceTest {
@Test
public void sliderScalesWithinRange() {
+ doReturn(true).when(mRbcSpy).isAvailable(mContext);
+
+ doReturn(85).when(mResourcesSpy).getInteger(
+ R.integer.config_reduceBrightColorsStrengthMax);
+ doReturn(10).when(mResourcesSpy).getInteger(
+ R.integer.config_reduceBrightColorsStrengthMin);
+ doReturn(44).when(mResourcesSpy).getInteger(
+ R.integer.config_reduceBrightColorsStrengthDefault);
+
// setup
startService();
- reset(mRbcSpy);
- doReturn(true).when(mRbcSpy).isAvailable(mContext);
- when(mContext.getResources().getInteger(
- R.integer.config_reduceBrightColorsStrengthMax)).thenReturn(85);
- when(mContext.getResources().getInteger(
- R.integer.config_reduceBrightColorsStrengthMin)).thenReturn(10);
- when(mContext.getResources().getInteger(
- R.integer.config_reduceBrightColorsStrengthDefault)).thenReturn(44);
// Valid value test //
// set on, and to 90% of range
diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index f9fbf1bd5085..b41f03bb8030 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -17,9 +17,9 @@
package com.android.server.display.whitebalance;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index 7c239ef02e58..586ff52aa78c 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -328,6 +328,7 @@ public class TestDreamEnvironment {
case DREAM_STATE_STARTED -> startDream();
case DREAM_STATE_WOKEN -> wakeDream();
}
+ mTestableLooper.processAllMessages();
} while (mCurrentDreamState < state);
return true;
diff --git a/services/tests/media/mediarouterservicetest/Android.bp b/services/tests/media/mediarouterservicetest/Android.bp
index aed3af6b69f6..f149f2ec8d56 100644
--- a/services/tests/media/mediarouterservicetest/Android.bp
+++ b/services/tests/media/mediarouterservicetest/Android.bp
@@ -9,6 +9,10 @@ package {
android_test {
name: "MediaRouterServiceTests",
+ defaults: [
+ // For ExtendedMockito dependencies.
+ "modules-utils-testable-device-config-defaults",
+ ],
srcs: [
"src/**/*.java",
],
@@ -23,12 +27,17 @@ android_test {
"services.core",
"truth",
],
+ libs: [
+ "android.test.base.stubs",
+ "android.test.runner.stubs",
+ ],
platform_apis: true,
test_suites: [
// "device-tests",
"general-tests",
+ "mts-statsd",
],
certificate: "platform",
@@ -36,4 +45,5 @@ android_test {
optimize: {
enabled: false,
},
+ min_sdk_version: "30",
}
diff --git a/services/tests/media/mediarouterservicetest/AndroidTest.xml b/services/tests/media/mediarouterservicetest/AndroidTest.xml
index b0656816b701..646812b076d6 100644
--- a/services/tests/media/mediarouterservicetest/AndroidTest.xml
+++ b/services/tests/media/mediarouterservicetest/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-tag" value="MediaRouterServiceTests" />
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="test-suite-tag" value="mts" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
@@ -26,7 +27,7 @@
<option name="install-arg" value="-t" />
</target_preparer>
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.server.media.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java
new file mode 100644
index 000000000000..5e401aefc24c
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media;
+
+import static android.media.MediaRoute2ProviderService.REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND;
+import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
+import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
+import static android.media.MediaRoute2ProviderService.REASON_UNIMPLEMENTED;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaRouterMetricLoggerTest {
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this).mockStatic(MediaRouterStatsLog.class).build();
+
+ private MediaRouterMetricLogger mLogger;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLogger = new MediaRouterMetricLogger();
+ }
+
+ @Test
+ public void addRequestInfo_addsRequestInfoToCache() {
+ long requestId = 123;
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+
+ mLogger.addRequestInfo(requestId, eventType);
+
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(1);
+ }
+
+ @Test
+ public void removeRequestInfo_removesRequestInfoFromCache() {
+ long requestId = 123;
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+ mLogger.addRequestInfo(requestId, eventType);
+
+ mLogger.removeRequestInfo(requestId);
+
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(0);
+ }
+
+ @Test
+ public void logOperationFailure_logsOperationFailure() {
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+ int result = MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+ mLogger.logOperationFailure(eventType, result);
+ verify(
+ () ->
+ MediaRouterStatsLog.write( // Use ExtendedMockito.verify and lambda
+ MEDIA_ROUTER_EVENT_REPORTED, eventType, result));
+ }
+
+ @Test
+ public void logRequestResult_logsRequestResult() {
+ long requestId = 123;
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+ int result = MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
+ mLogger.addRequestInfo(requestId, eventType);
+
+ mLogger.logRequestResult(requestId, result);
+
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(0);
+ verify(
+ () ->
+ MediaRouterStatsLog.write( // Use ExtendedMockito.verify and lambda
+ MEDIA_ROUTER_EVENT_REPORTED, eventType, result));
+ }
+
+ @Test
+ public void convertResultFromReason_returnsCorrectResult() {
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_UNKNOWN_ERROR))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_REJECTED))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_NETWORK_ERROR))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_ROUTE_NOT_AVAILABLE))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_INVALID_COMMAND))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_UNIMPLEMENTED))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED);
+ assertThat(
+ MediaRouterMetricLogger.convertResultFromReason(
+ REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA))
+ .isEqualTo(
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(-1))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
+ }
+
+ @Test
+ public void getRequestCacheSize_returnsCorrectSize() {
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(0);
+ mLogger.addRequestInfo(
+ 123, MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION);
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(1);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index eda5e8613dba..77d67019c0ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -67,6 +67,9 @@ import java.util.concurrent.TimeUnit;
/**
* Test RescueParty.
+ * TODO: b/354112511 delete this file
+ * Moved to frameworks/base/tests/PackageWatchdog/src/com/android/server/RescuePartyTest
+ *
*/
public class RescuePartyTest {
private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 2a513ae3a8e8..f0e61ec9c692 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -109,7 +109,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.Manifest;
import android.app.ActivityManager;
@@ -953,11 +953,13 @@ public final class AlarmManagerServiceTest {
@Test
@EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND)
- public void testWakelockOrdering() throws Exception {
+ public void testWakelockOrderingFirstAlarm() throws Exception {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+ // Pretend that it is the first alarm in this batch, or no other alarms are still processing
+ mService.mBroadcastRefCount = 0;
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
@@ -975,20 +977,51 @@ public final class AlarmManagerServiceTest {
@Test
@EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND)
- public void testWakelockReleasedWhenSendFails() throws Exception {
+ public void testWakelockOrderingNonFirst() throws Exception {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+ // Pretend that some previous alarms are still processing.
+ mService.mBroadcastRefCount = 3;
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+
+ final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor =
+ ArgumentCaptor.forClass(PendingIntent.OnFinished.class);
+ verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), onFinishedCaptor.capture(),
+ any(Handler.class), isNull(), any());
+ onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null);
+
+ verify(mWakeLock, never()).acquire();
+ verify(mWakeLock, never()).release();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND)
+ public void testWakelockReleasedWhenSendFails() throws Exception {
+ final PendingIntent alarmPi = getNewMockPendingIntent();
doThrow(new PendingIntent.CanceledException("test")).when(alarmPi).send(eq(mMockContext),
eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any());
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5000, alarmPi);
+
+ // Pretend that it is the first alarm in this batch, or no other alarms are still processing
+ mService.mBroadcastRefCount = 0;
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
final InOrder inOrder = Mockito.inOrder(mWakeLock);
inOrder.verify(mWakeLock).acquire();
inOrder.verify(mWakeLock).release();
+
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5000, alarmPi);
+
+ // Pretend that some previous alarms are still processing.
+ mService.mBroadcastRefCount = 4;
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ inOrder.verifyNoMoreInteractions();
}
@Test
@@ -3691,8 +3724,8 @@ public final class AlarmManagerServiceTest {
setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 0);
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
- verifyZeroInteractions(mPackageManagerInternal);
- verifyZeroInteractions(mService.mHandler);
+ verifyNoMoreInteractions(mPackageManagerInternal);
+ verifyNoMoreInteractions(mService.mHandler);
}
private void testTemporaryQuota_bumpedAfterDeferral(int standbyBucket) throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index 7dab1c854625..859d2d2f2e38 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -30,7 +30,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -244,7 +244,7 @@ public class AlarmStoreTest {
addAlarmsToStore(simpleAlarm, alarmClock);
mAlarmStore.remove(simpleAlarm::equals);
- verifyZeroInteractions(onRemoved);
+ verifyNoMoreInteractions(onRemoved);
mAlarmStore.remove(alarmClock::equals);
verify(onRemoved).run();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 35ab2d233563..acc06d0c7cba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -74,7 +74,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.Manifest;
import android.app.ActivityManager;
@@ -584,7 +583,7 @@ public class ActivityManagerServiceTest {
if (app.uid == uidRec.getUid() && expectedBlockState == NETWORK_STATE_BLOCK) {
verify(app.getThread()).setNetworkBlockSeq(uidRec.curProcStateSeq);
} else {
- verifyZeroInteractions(app.getThread());
+ verifyNoMoreInteractions(app.getThread());
}
Mockito.reset(app.getThread());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 194bf4ba73f3..84110ae5cd02 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -34,9 +34,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index e678acc092e9..987b9c6427d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -21,6 +21,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.server.am.ActivityManagerService.Injector;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -625,6 +626,60 @@ public class ApplicationStartInfoTest {
assertTrue(startInfo.equals(startInfoFromParcel));
}
+ /** Test that new timestamps are added to the correct record (the most recently created one). */
+ @Test
+ public void testTimestampAddedToCorrectRecord() throws Exception {
+ // Use a different start timestamp for each record so we can identify which was added to.
+ final long startTimeRecord1 = 123L;
+ final long startTimeRecord2 = 456L;
+
+ final long forkTime = 789L;
+
+ // Create a process record to use with all starts.
+ ProcessRecord app = makeProcessRecord(
+ APP_1_PID_1, // pid
+ APP_1_UID, // uid
+ APP_1_UID, // packageUid
+ null, // definingUid
+ APP_1_PROCESS_NAME, // processName
+ APP_1_PACKAGE_NAME); // packageName
+
+ // Trigger a start info record.
+ mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord1, app,
+ buildIntent(COMPONENT), false /* isAlarm */);
+
+ // Wait at least 1 ms for monotonic time to increase.
+ sleep(1);
+
+ // Verify the record was added successfully.
+ ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+ mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+ assertEquals(1, list.size());
+ assertEquals(startTimeRecord1, list.get(0).getStartupTimestamps().get(0).longValue());
+
+ // Now trigger another start info record.
+ mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord2, app,
+ buildIntent(COMPONENT), false /* isAlarm */);
+
+ // Add a timestamp to the most recent record.
+ mAppStartInfoTracker.addTimestampToStart(
+ app, forkTime, ApplicationStartInfo.START_TIMESTAMP_FORK);
+
+ // Verify the record was added successfully.
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+ assertEquals(2, list.size());
+ assertEquals(startTimeRecord2, list.get(0).getStartupTimestamps().get(0).longValue());
+ assertEquals(startTimeRecord1, list.get(1).getStartupTimestamps().get(0).longValue());
+
+ // Verify that the new timestamp is set correctly on the 2nd record that was added and not
+ // on the first.
+ assertEquals(forkTime, list.get(0).getStartupTimestamps()
+ .get(ApplicationStartInfo.START_TIMESTAMP_FORK).longValue());
+ assertFalse(list.get(1).getStartupTimestamps().containsKey(
+ ApplicationStartInfo.START_TIMESTAMP_FORK));
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 637c73f31ce0..dd5924ad4a7e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -88,7 +88,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.clearInvocations;
@@ -747,7 +747,7 @@ public final class BackgroundRestrictionTest {
mCurrentTimeMillis = 10_000L;
doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp();
- doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+ doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(any());
mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
testPid, true);
mAppFGSTracker.onForegroundServiceNotificationUpdated(
@@ -1925,7 +1925,7 @@ public final class BackgroundRestrictionTest {
mCurrentTimeMillis = 10_000L;
doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp();
- doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+ doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(any());
// Run with a media playback service which starts/stops immediately, we should
// goto the restricted bucket.
@@ -3170,7 +3170,7 @@ public final class BackgroundRestrictionTest {
inv.getArgument(2));
return null;
}).when(telephonyManager).registerCarrierPrivilegesCallback(
- anyInt(), anyObject(), anyObject());
+ anyInt(), any(), any());
}
public void registerCarrierPrivilegesCallback(int phoneId, Executor executor,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java
new file mode 100644
index 000000000000..7d8d9a57aaa1
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.am;
+
+import static android.app.AppProtoEnums.BROADCAST_TYPE_FOREGROUND;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_STICKY;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_PROCESSED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+@SmallTest
+public class BroadcastProcessedEventRecordTest {
+
+ private static final String ACTION = "action";
+ private static final String PROCESS_NAME = "process";
+ private static final int[] BROADCAST_TYPES =
+ new int[]{BROADCAST_TYPE_FOREGROUND, BROADCAST_TYPE_STICKY};
+
+ private BroadcastProcessedEventRecord mBroadcastProcessedEventRecord;
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(
+ this).mockStatic(FrameworkStatsLog.class).build();
+
+ @Before
+ public void setUp() {
+ mBroadcastProcessedEventRecord = createBroadcastProcessEventRecord();
+ }
+
+ @Test
+ public void addReceiverFinishDetails_withNewRecord_updatesBroadcastRecordEventTime() {
+ assertThat(mBroadcastProcessedEventRecord.getReceiverUidForTest()).isEqualTo(SYSTEM_UID);
+ assertThat(mBroadcastProcessedEventRecord.getSenderUidForTest()).isEqualTo(SYSTEM_UID);
+ assertThat(mBroadcastProcessedEventRecord.getIntentActionForTest()).isEqualTo(ACTION);
+ assertThat(mBroadcastProcessedEventRecord.getReceiverProcessNameForTest()).isEqualTo(
+ PROCESS_NAME);
+ assertThat(mBroadcastProcessedEventRecord.getBroadcastTypesForTest()).isEqualTo(
+ BROADCAST_TYPES);
+
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(20);
+ verifyBroadcastProcessEventUpdateRecord(
+ /* numberOfReceivers= */ 1,
+ /* totalBroadcastFinishTimeMillis= */ 20,
+ /* maxReceiverFinishTimeMillis= */ 20);
+
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(25);
+ verifyBroadcastProcessEventUpdateRecord(
+ /* numberOfReceivers= */ 2,
+ /* totalBroadcastFinishTimeMillis= */ 45,
+ /* maxReceiverFinishTimeMillis= */ 25);
+
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(10);
+ verifyBroadcastProcessEventUpdateRecord(
+ /* numberOfReceivers= */ 3,
+ /* totalBroadcastFinishTimeMillis= */ 55,
+ /* maxReceiverFinishTimeMillis= */ 25);
+ }
+
+ @Test
+ public void logToStatsD_loggingSuccessful() {
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(20);
+ mBroadcastProcessedEventRecord.logToStatsD();
+
+ ExtendedMockito.verify(() -> FrameworkStatsLog.write(eq(BROADCAST_PROCESSED),
+ eq(ACTION),
+ eq(SYSTEM_UID),
+ eq(SYSTEM_UID),
+ eq(1),
+ eq(PROCESS_NAME),
+ eq(20L),
+ eq(20L),
+ eq(BROADCAST_TYPES)));
+ }
+
+ @Test
+ public void logToStatsD_withTotalTimeLessThanTenMs_NoLogging() {
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(8);
+ mBroadcastProcessedEventRecord.logToStatsD();
+
+ ExtendedMockito.verify(() -> FrameworkStatsLog.write(eq(BROADCAST_PROCESSED),
+ eq(ACTION),
+ eq(SYSTEM_UID),
+ eq(SYSTEM_UID),
+ eq(1),
+ eq(PROCESS_NAME),
+ eq(8L),
+ eq(8L),
+ eq(BROADCAST_TYPES)), Mockito.never());
+ }
+
+ private BroadcastProcessedEventRecord createBroadcastProcessEventRecord() {
+ return new BroadcastProcessedEventRecord()
+ .setBroadcastTypes(BROADCAST_TYPES)
+ .setIntentAction(ACTION)
+ .setReceiverProcessName(PROCESS_NAME)
+ .setReceiverUid(SYSTEM_UID)
+ .setSenderUid(SYSTEM_UID);
+ }
+
+ private void verifyBroadcastProcessEventUpdateRecord(
+ int numberOfReceivers,
+ long totalBroadcastFinishTimeMillis,
+ long maxReceiverFinishTimeMillis) {
+ assertThat(mBroadcastProcessedEventRecord.getNumberOfReceiversForTest())
+ .isEqualTo(numberOfReceivers);
+ assertThat(mBroadcastProcessedEventRecord.getTotalBroadcastFinishTimeMillisForTest())
+ .isEqualTo(totalBroadcastFinishTimeMillis);
+ assertThat(mBroadcastProcessedEventRecord.getMaxReceiverFinishTimeMillisForTest())
+ .isEqualTo(maxReceiverFinishTimeMillis);
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
index b32ce49d049d..6726088c6c61 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
@@ -87,6 +87,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.io.PrintWriter;
import java.io.Writer;
@@ -1769,6 +1770,33 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest {
}
@Test
+ public void testBroadcastProcessedEventRecord_broadcastDelivered_processedEventLogged()
+ throws Exception {
+ testBroadcastProcessedEventRecordLogged(
+ /* isAssumedDelivered= */ false,
+ /* isDelivered= */ true,
+ /* numberOfInvocations= */ 1);
+ }
+
+ @Test
+ public void testBroadcastProcessedEventRecord_broadcastDeliveryFailed_eventNotLogged()
+ throws Exception {
+ testBroadcastProcessedEventRecordLogged(
+ /* isAssumedDelivered= */ false,
+ /* isDelivered= */ false,
+ /* numberOfInvocations= */ 0);
+ }
+
+ @Test
+ public void testBroadcastProcessedEventRecord_broadcastAssumedDelivered_eventNotLogged()
+ throws Exception {
+ testBroadcastProcessedEventRecordLogged(
+ /* isAssumedDelivered= */ true,
+ /* isDelivered= */ true,
+ /* numberOfInvocations= */ 0);
+ }
+
+ @Test
public void testGetPreferredSchedulingGroup() throws Exception {
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
@@ -2310,6 +2338,28 @@ public final class BroadcastQueueImplTest extends BaseBroadcastQueueTest {
assertFalse(mImpl.isProcessFreezable(greenProcess));
}
+ @SuppressWarnings("GuardedBy")
+ private void testBroadcastProcessedEventRecordLogged(
+ boolean isAssumedDelivered,
+ boolean isDelivered,
+ int numberOfInvocations) throws Exception {
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
+ optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ final BroadcastRecord broadcastRecordSpy = Mockito.spy(
+ makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(broadcastRecordSpy);
+
+ doReturn(isDelivered).when(broadcastRecordSpy).wasDelivered(anyInt());
+ doReturn(isAssumedDelivered).when(broadcastRecordSpy).isAssumedDelivered(anyInt());
+ waitForIdle();
+
+ verify(broadcastRecordSpy, times(numberOfInvocations)).updateBroadcastProcessedEventRecord(
+ any(), anyLong());
+ verify(broadcastRecordSpy).logBroadcastProcessedEventRecord();
+ }
+
BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
final IIntentReceiver receiver = mock(IIntentReceiver.class);
final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3a9c99d57d71..d540b2ec13eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -155,7 +155,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting startProcessLocked() for "
+ Arrays.toString(invocation.getArguments()));
- assertHealth();
final String processName = invocation.getArgument(0);
final ProcessStartBehavior behavior = mNewProcessStartBehaviors.getOrDefault(
processName, mNextProcessStartBehavior.getAndSet(ProcessStartBehavior.SUCCESS));
@@ -206,6 +205,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
mActiveProcesses.remove(res);
res.setKilled(true);
break;
+ case MISSING_RESPONSE:
+ res.setPendingStart(true);
+ break;
default:
throw new UnsupportedOperationException();
}
@@ -244,6 +246,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
mConstants.MAX_FROZEN_OUTGOING_BROADCASTS = 10;
+ mConstants.PENDING_COLD_START_ABANDON_TIMEOUT_MILLIS = 2000;
}
@After
@@ -279,6 +282,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
FAIL_NULL,
/** Process is killed without reporting to BroadcastQueue */
KILLED_WITHOUT_NOTIFY,
+ /** Process start fails without no response */
+ MISSING_RESPONSE,
}
private enum ProcessBehavior {
@@ -1173,6 +1178,37 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
}
+ @Test
+ public void testProcessStartWithMissingResponse() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+ mNewProcessStartBehaviors.put(PACKAGE_GREEN, ProcessStartBehavior.MISSING_RESPONSE);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+ withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+ waitForIdle();
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+ getUidForPackage(PACKAGE_ORANGE));
+
+ verifyScheduleReceiver(times(1), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+ verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+ }
+
/**
* Verify that a broadcast sent to a frozen app, which gets killed as part of unfreezing
* process due to pending sync binder transactions, is delivered as expected.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 8482fd609d05..3ea83debe2bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -30,14 +30,20 @@ import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
@@ -66,6 +72,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@@ -93,6 +100,9 @@ public class BroadcastRecordTest {
private static final String PACKAGE2 = "pkg2";
private static final String PACKAGE3 = "pkg3";
+ private static final String PROCESS1 = "process1";
+ private static final String PROCESS2 = "process2";
+
private static final int SYSTEM_UID = android.os.Process.SYSTEM_UID;
private static final int APP_UID = android.os.Process.FIRST_APPLICATION_UID;
@@ -1005,6 +1015,142 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE3, getAppId(3)))));
}
+
+ @Test
+ @DisableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testUpdateBroadcastProcessedEventRecord_flagDisabled() {
+ final ResolveInfo receiver = createResolveInfo(PACKAGE1, getAppId(1));
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver));
+
+ record.updateBroadcastProcessedEventRecord(receiver, 10);
+
+ assertThat(record.getBroadcastProcessedRecordsForTest()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testUpdateBroadcastProcessedEventRecord_withNewReceiver_newBroadcastProcessedEventRecordCreated() {
+ final ResolveInfo receiver =
+ createResolveInfoWithProcessName(PACKAGE1, getAppId(1), PROCESS1);
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver));
+
+ record.updateBroadcastProcessedEventRecord(receiver, 10);
+
+ assertThat(record.getBroadcastProcessedRecordsForTest()).isNotEmpty();
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord =
+ record.getBroadcastProcessedRecordsForTest().get(
+ BroadcastRecord.getReceiverProcessName(receiver));
+
+ assertBroadcastProcessedEvent(
+ broadcastProcessedEventRecord,
+ 10001,
+ PROCESS1,
+ 1,
+ 2,
+ 10,
+ 10);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testUpdateBroadcastProcessedEventRecord_withNewAndExistingReceiver_multipleBroadcastProcessedEventRecordCreated() {
+ final ResolveInfo receiver1 =
+ createResolveInfoWithProcessName(PACKAGE1, getAppId(1), PROCESS1);
+
+ final ResolveInfo receiver2 =
+ createResolveInfoWithProcessName(PACKAGE2, getAppId(2), PROCESS2);
+
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver1, receiver2));
+
+ record.updateBroadcastProcessedEventRecord(receiver1, 11);
+ record.updateBroadcastProcessedEventRecord(receiver2, 11);
+ record.updateBroadcastProcessedEventRecord(receiver1, 20);
+
+ assertThat(record.getBroadcastProcessedRecordsForTest().size()).isEqualTo(2);
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord1 =
+ record.getBroadcastProcessedRecordsForTest().get(
+ BroadcastRecord.getReceiverProcessName(receiver1));
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord2 =
+ record.getBroadcastProcessedRecordsForTest().get(
+ BroadcastRecord.getReceiverProcessName(receiver2));
+
+ assertBroadcastProcessedEvent(
+ broadcastProcessedEventRecord1,
+ 10001,
+ PROCESS1,
+ 2,
+ 1,
+ 31,
+ 20);
+ assertBroadcastProcessedEvent(
+ broadcastProcessedEventRecord2,
+ 10002,
+ PROCESS2,
+ 1,
+ 1,
+ 11,
+ 11);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testLogBroadcastProcessedEventRecord_flagDisabled() {
+ testLogBroadcastProcessedEventRecord(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testLogBroadcastProcessedEventRecord_flagEnabled_allBroadcastProcessedEventLogged() {
+ testLogBroadcastProcessedEventRecord(1);
+ }
+
+ private void testLogBroadcastProcessedEventRecord(int times) {
+ final ResolveInfo receiver = createResolveInfo(PACKAGE1, getAppId(1));
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver));
+
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord = Mockito.mock(
+ BroadcastProcessedEventRecord.class);
+ record.getBroadcastProcessedRecordsForTest()
+ .put("process", broadcastProcessedEventRecord);
+ record.logBroadcastProcessedEventRecord();
+ doNothing().when(broadcastProcessedEventRecord).logToStatsD();
+
+ verify(broadcastProcessedEventRecord, times(times)).logToStatsD();
+ }
+
+ private void assertBroadcastProcessedEvent(
+ BroadcastProcessedEventRecord broadcastProcessedEventRecord,
+ int receiverUid,
+ String processName,
+ int numberOfReceivers,
+ int broadcastTypeLength,
+ long totalBroadcastFinishTimeMillis,
+ long maxReceiverFinishTimeMillis) {
+ assertNotNull(broadcastProcessedEventRecord);
+ assertThat(broadcastProcessedEventRecord.getReceiverUidForTest()).isEqualTo(receiverUid);
+ assertThat(broadcastProcessedEventRecord.getSenderUidForTest()).isEqualTo(0);
+ assertThat(broadcastProcessedEventRecord.getIntentActionForTest())
+ .isEqualTo(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ assertThat(broadcastProcessedEventRecord.getReceiverProcessNameForTest())
+ .isEqualTo(processName);
+ assertThat(broadcastProcessedEventRecord.getBroadcastTypesForTest().length)
+ .isEqualTo(broadcastTypeLength);
+ assertThat(broadcastProcessedEventRecord.getNumberOfReceiversForTest())
+ .isEqualTo(numberOfReceivers);
+ assertThat(broadcastProcessedEventRecord.getTotalBroadcastFinishTimeMillisForTest())
+ .isEqualTo(totalBroadcastFinishTimeMillis);
+ assertThat(broadcastProcessedEventRecord.getMaxReceiverFinishTimeMillisForTest())
+ .isEqualTo(maxReceiverFinishTimeMillis);
+ }
+
private boolean[] calculateChangeState(List<Object> receivers) {
return BroadcastRecord.calculateChangeStateForReceivers(receivers,
LIMIT_PRIORITY_SCOPE, mPlatformCompat);
@@ -1049,6 +1195,16 @@ public class BroadcastRecordTest {
return createResolveInfo(PACKAGE1, getAppId(1), priority);
}
+ private static ResolveInfo createResolveInfoWithProcessName(
+ String packageName,
+ int uid,
+ String processName) {
+ final ResolveInfo resolveInfo = createResolveInfo(packageName, uid);
+ resolveInfo.activityInfo.processName = processName;
+
+ return resolveInfo;
+ }
+
private static ResolveInfo createResolveInfo(String packageName, int uid) {
return createResolveInfo(packageName, uid, 0);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 340115a7d465..e094111c327a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -43,6 +43,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -107,6 +108,7 @@ import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -698,7 +700,7 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
- @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ @EnableFlags({Flags.FLAG_USE_CPU_TIME_CAPABILITY, Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING})
public void testUpdateOomAdjFreezeState_bindingFromShortFgs() {
// Setting up a started short FGS within app1.
final ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
@@ -744,6 +746,74 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
@EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ public void testUpdateOomAdjFreezeState_bindingWithAllowFreeze() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ WindowProcessController wpc = app.getWindowProcessController();
+ doReturn(true).when(wpc).hasVisibleActivities();
+
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+
+ // App with a visible activity binds to app2 without any special flag.
+ bindService(app2, app, null, null, 0, mock(IBinder.class));
+
+ final ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+
+ // App with a visible activity binds to app3 with ALLOW_FREEZE.
+ bindService(app3, app, null, null, Context.BIND_ALLOW_FREEZE, mock(IBinder.class));
+
+ setProcessesToLru(app, app2, app3);
+
+ updateOomAdj(app);
+
+ assertCpuTime(app);
+ assertCpuTime(app2);
+ assertNoCpuTime(app3);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING)
+ public void testUpdateOomAdjFreezeState_bindingFromFgs() {
+ final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE, false);
+
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ // App with a foreground service binds to app2
+ bindService(app2, app, null, null, 0, mock(IBinder.class));
+
+ setProcessesToLru(app, app2);
+ updateOomAdj(app);
+
+ assertCpuTime(app);
+ assertCpuTime(app2);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING)
+ public void testUpdateOomAdjFreezeState_soloFgs() {
+ final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE, false);
+
+ setProcessesToLru(app);
+ updateOomAdj(app);
+
+ assertCpuTime(app);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
public void testUpdateOomAdjFreezeState_receivers() {
final ProcessRecord app = makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index d6349fc0651f..ab3784b07e10 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -32,7 +32,6 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED
import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
-import static com.android.window.flags.Flags.balClearAllowlistDuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -216,9 +215,7 @@ public class PendingIntentControllerTest {
pir.getAllowlistDurationLocked(token);
assertNotNull(allowlistDurationLockedAfterClear);
assertEquals(1000, allowlistDurationLockedAfterClear.duration);
- assertEquals(balClearAllowlistDuration()
- ? TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED
- : TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
allowlistDurationLocked.type);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
index 8aaa72339c5b..33bd95ec9f5b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
@@ -51,6 +51,7 @@ import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
+import com.android.server.backup.BackupRestoreTask.CancellationReason;
import com.android.server.backup.internal.LifecycleOperationStorage;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -368,9 +369,12 @@ public class BackupAgentConnectionManagerTest {
mConnectionManager.agentDisconnected(TEST_PACKAGE);
mTestThread.join();
- verify(mUserBackupManagerService).handleCancel(eq(123), eq(true));
- verify(mUserBackupManagerService).handleCancel(eq(456), eq(true));
- verify(mUserBackupManagerService).handleCancel(eq(789), eq(true));
+ verify(mUserBackupManagerService)
+ .handleCancel(eq(123), eq(CancellationReason.AGENT_DISCONNECTED));
+ verify(mUserBackupManagerService)
+ .handleCancel(eq(456), eq(CancellationReason.AGENT_DISCONNECTED));
+ verify(mUserBackupManagerService)
+ .handleCancel(eq(789), eq(CancellationReason.AGENT_DISCONNECTED));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
index ae0452a60161..b7087c74bf8d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
@@ -105,7 +105,7 @@ public class FullBackupUtilsTest {
} catch (EOFException expected) {
}
- verifyZeroInteractions(mOutputStreamMock);
+ verifyNoMoreInteractions(mOutputStreamMock);
assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
}
@@ -126,7 +126,7 @@ public class FullBackupUtilsTest {
} catch (EOFException expected) {
}
- verifyZeroInteractions(mOutputStreamMock);
+ verifyNoMoreInteractions(mOutputStreamMock);
assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
}
@@ -141,7 +141,7 @@ public class FullBackupUtilsTest {
FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor, mOutputStreamMock);
- verifyZeroInteractions(mOutputStreamMock);
+ verifyNoMoreInteractions(mOutputStreamMock);
assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index bf7e3a0bd0a6..346d5f787621 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -32,7 +32,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.app.backup.IBackupManagerMonitor;
@@ -239,7 +238,7 @@ public class TarBackupReaderTest {
mMockPackageManagerInternal, mUserId, mContext);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
- verifyZeroInteractions(mBackupManagerMonitorMock);
+ verifyNoMoreInteractions(mBackupManagerMonitorMock);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index e030b3f19e4f..16adf8f8c7fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -60,4 +60,8 @@ android_test {
"mts-crashrecovery",
],
min_sdk_version: "36",
+
+ // Test coverage system runs on different devices. Need to
+ // compile for all architecture.
+ compile_multilib: "both",
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 3e8794377d37..2d84887afb41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1029,6 +1029,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1127,6 +1128,54 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes_Tuning() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
+ expectedStats.expirationTimeElapsed = now + 34 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 5;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ // There is only one session in the past active bucket window, the empty time for this
+ // window is the bucket window size - duration of the session.
+ expectedStats.expirationTimeElapsed = now + 54 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
*/
@@ -1195,6 +1244,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes() {
// Set time to 3 minutes after boot.
advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
@@ -1206,10 +1256,10 @@ public class QuotaControllerTest {
ExecutionStats expectedStats = new ExecutionStats();
// Exempted
- expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
- expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
- expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
expectedStats.expirationTimeElapsed = 10 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
expectedStats.bgJobCountInWindow = 2;
@@ -1268,6 +1318,49 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes_Tunning() {
+ // Set time to 3 minutes after boot.
+ advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+ advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
+ expectedStats.expirationTimeElapsed = 30 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 2;
+ expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 2;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 50 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
*/
@@ -1425,6 +1518,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes() {
for (int i = 0; i < 20; ++i) {
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1581,6 +1675,165 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes_Tuning() {
+ for (int i = 0; i < 20; ++i) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 5 * MINUTE_IN_MILLIS, 5), false);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ for (int j = 0; j < 5; ++j) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ MINUTE_IN_MILLIS, 2), false);
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ advanceElapsedClock(54 * SECOND_IN_MILLIS);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
+ advanceElapsedClock(500);
+ advanceElapsedClock(400);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
+ advanceElapsedClock(100);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ }
+ advanceElapsedClock(40 * MINUTE_IN_MILLIS);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(16, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(64, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(192, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(320, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(11, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 5 TimingSessions are coalesced
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 5 TimingSessions are coalesced
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 5 TimingSessions are coalesced
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(11, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * SECOND_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(7, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 9 TimingSessions are coalesced
+ assertEquals(28, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 9 TimingSessions are coalesced
+ assertEquals(84, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 9 TimingSessions are coalesced
+ assertEquals(140, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ MINUTE_IN_MILLIS);
+
+ // Only two TimingSessions there for every hour.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(2, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(8, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(24, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(40, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * MINUTE_IN_MILLIS);
+
+ // Only one TimingSessions there for every hour
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(1, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 15 * MINUTE_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(1, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
+ // between an hour and 15 minutes.
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(1, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
*/
@@ -2231,32 +2484,6 @@ public class QuotaControllerTest {
}
}
- @Test
- @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
- public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
- false);
-
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
- 20 * MINUTE_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
- // window size = allowed time, so jobs can essentially run non-stop until they reach the
- // max execution time.
- setStandbyBucket(EXEMPTED_INDEX);
- synchronized (mQuotaController.mLock) {
- assertEquals(10 * MINUTE_IN_MILLIS,
- mQuotaController.getRemainingExecutionTimeLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
- mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- }
- }
-
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window.
@@ -2327,6 +2554,7 @@ public class QuotaControllerTest {
@Test
@EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Close to RARE boundary.
@@ -2390,7 +2618,30 @@ public class QuotaControllerTest {
@Test
@EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes_Tuning() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to ACTIVE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+
+ // ACTIVE window != allowed time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(17 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(20 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER})
+ @DisableFlags(Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS)
public void testGetTimeUntilQuotaConsumedLocked_Installer() {
PackageInfo pi = new PackageInfo();
pi.packageName = SOURCE_PACKAGE;
@@ -2412,7 +2663,7 @@ public class QuotaControllerTest {
// Far away from FREQUENT boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
2 * MINUTE_IN_MILLIS, 5), false);
// Overlap WORKING_SET boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2422,12 +2673,12 @@ public class QuotaControllerTest {
// Close to ACTIVE boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
2 * MINUTE_IN_MILLIS, 5), false);
// Close to EXEMPTED boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(
- now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS),
+ now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS),
2 * MINUTE_IN_MILLIS, 5), false);
// No additional quota for the system installer when the app is in RARE, FREQUENT,
@@ -2486,6 +2737,42 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
+ Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER,
+ Flags.FLAG_TUNE_QUOTA_WINDOW_DEFAULT_PARAMETERS})
+ public void testGetTimeUntilQuotaConsumedLocked_Installer_Tuning() {
+ PackageInfo pi = new PackageInfo();
+ pi.packageName = SOURCE_PACKAGE;
+ pi.requestedPermissions = new String[]{Manifest.permission.INSTALL_PACKAGES};
+ pi.requestedPermissionsFlags = new int[]{PackageInfo.REQUESTED_PERMISSION_GRANTED};
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = mSourceUid;
+ doReturn(List.of(pi)).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
+ eq(Manifest.permission.INSTALL_PACKAGES), anyInt(), eq(mSourceUid));
+ mQuotaController.onSystemServicesReady();
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to EXEMPTED boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS - MINUTE_IN_MILLIS),
+ 2 * MINUTE_IN_MILLIS, 5), false);
+
+ // Additional quota for the system installer when the app is in EXEMPTED bucket.
+ // EXEMPTED window == allowed time.
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(38 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 2 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
*/
@@ -2637,33 +2924,6 @@ public class QuotaControllerTest {
}
}
- @Test
- @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
- public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS),
- mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, 5),
- false);
- mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (20 * MINUTE_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5),
- false);
-
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
- 20 * MINUTE_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
- // window size != allowed time.
- setStandbyBucket(EXEMPTED_INDEX);
- synchronized (mQuotaController.mLock) {
- assertEquals(0,
- mQuotaController.getRemainingExecutionTimeLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS,
- mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
- }
- }
-
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window and the session is rolling out of the window.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index 3d0c63780ef3..29af7d28339d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.AssertJUnit.assertEquals;
@@ -128,7 +128,7 @@ public class BackgroundUserSoundNotifierTest {
AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
- verifyZeroInteractions(mNotificationManager);
+ verifyNoMoreInteractions(mNotificationManager);
}
@Test
@@ -143,7 +143,7 @@ public class BackgroundUserSoundNotifierTest {
Build.VERSION.SDK_INT);
clearInvocations(mNotificationManager);
mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi);
- verifyZeroInteractions(mNotificationManager);
+ verifyNoMoreInteractions(mNotificationManager);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index 8ce05e2fa115..c9f86b04be22 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -23,7 +23,6 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
-import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED;
import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED;
@@ -49,6 +48,7 @@ import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* Tests for {@link com.android.server.power.ScreenUndimDetector}
@@ -61,7 +61,8 @@ public class ScreenUndimDetectorTest {
POLICY_DIM,
POLICY_BRIGHT);
private static final int OTHER_DISPLAY_GROUP = DEFAULT_DISPLAY_GROUP + 1;
-
+ private static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
+ TimeUnit.MINUTES.toMillis(5);
@ClassRule
public static final TestableContext sContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@@ -88,7 +89,8 @@ public class ScreenUndimDetectorTest {
@Before
public void setup() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED, Boolean.TRUE.toString(), false /*makeDefault*/);
DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_UNDIMS_REQUIRED,
Integer.toString(1), false /*makeDefault*/);
@@ -108,10 +110,10 @@ public class ScreenUndimDetectorTest {
@Test
public void recordScreenPolicy_disabledByFlag_noop() {
+ setup();
DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
mScreenUndimDetector.readValuesFromDeviceConfig();
-
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_DIM);
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT);
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index 36b064b9b090..7c02370ef758 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -58,6 +58,10 @@ android_test {
"mts-crashrecovery",
],
min_sdk_version: "36",
+
+ // Test coverage system runs on different devices. Need to
+ // compile for all architecture.
+ compile_multilib: "both",
}
test_module_config {
diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
index 8257168f8d08..8257168f8d08 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index dc04b6aea318..bf3fe8c70bf1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.content.Context;
@@ -176,9 +176,9 @@ public class CameraPrivacyLightControllerTest {
prepareCameraPrivacyLightController(List.of(getNextLight(true)), Collections.EMPTY_SET,
true, new int[0], mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis);
- verifyZeroInteractions(mLightsManager);
- verifyZeroInteractions(mAppOpsManager);
- verifyZeroInteractions(mSensorManager);
+ verifyNoMoreInteractions(mLightsManager);
+ verifyNoMoreInteractions(mAppOpsManager);
+ verifyNoMoreInteractions(mSensorManager);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java
index 52cd29cabb94..1415dd690a0a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java
@@ -22,10 +22,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.contains;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.matches;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 49c37f163ff2..4f74667f3ff2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -33,27 +33,29 @@ import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
-import android.view.Display;
import android.view.DisplayInfo;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
import org.junit.AfterClass;
import org.junit.Before;
@@ -70,10 +72,11 @@ import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
/**
* Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
- * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ * {@link WallpaperCropper#getCrop(Point, WallpaperDefaultDisplayInfo, Point, SparseArray, boolean)}.
*/
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -83,7 +86,12 @@ public class WallpaperCropperTest {
@Mock
private WallpaperDisplayHelper mWallpaperDisplayHelper;
- private WallpaperCropper mWallpaperCropper;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private Resources mResources;
private static final Point PORTRAIT_ONE = new Point(500, 800);
private static final Point PORTRAIT_TWO = new Point(400, 1000);
@@ -159,7 +167,6 @@ public class WallpaperCropperTest {
return getWallpaperTestDir(userId);
}).when(() -> WallpaperUtils.getWallpaperDir(anyInt()));
- mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
}
private File getWallpaperTestDir(int userId) {
@@ -175,14 +182,21 @@ public class WallpaperCropperTest {
return tempDir;
}
- private void setUpWithDisplays(List<Point> displaySizes) {
+ private WallpaperDefaultDisplayInfo setUpWithDisplays(List<Point> displaySizes) {
mDisplaySizes = new SparseArray<>();
displaySizes.forEach(size -> {
mDisplaySizes.put(getOrientation(size), size);
Point rotated = new Point(size.y, size.x);
mDisplaySizes.put(getOrientation(rotated), rotated);
});
+ Set<WindowMetrics> windowMetrics = new ArraySet<>();
+ for (Point displaySize : displaySizes) {
+ windowMetrics.add(
+ new WindowMetrics(new Rect(0, 0, displaySize.x, displaySize.y),
+ new WindowInsets.Builder().build()));
+ }
when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())).thenReturn(windowMetrics);
if (displaySizes.size() == 2) {
Point largestDisplay = displaySizes.stream().max(
Comparator.comparingInt(p -> p.x * p.y)).get();
@@ -192,11 +206,16 @@ public class WallpaperCropperTest {
mFolded = getOrientation(smallestDisplay);
mUnfoldedRotated = getRotatedOrientation(mUnfolded);
mFoldedRotated = getRotatedOrientation(mFolded);
+ // foldable
+ doReturn(new int[]{0}).when(mResources).getIntArray(R.array.config_foldedDeviceStates);
+ } else {
+ // no foldable
+ doReturn(new int[]{}).when(mResources).getIntArray(R.array.config_foldedDeviceStates);
}
- doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
- .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
- doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
- .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+ when(mWallpaperDisplayHelper.getDefaultDisplayInfo()).thenReturn(defaultDisplayInfo);
+ return defaultDisplayInfo;
}
private int getFoldedOrientation(int orientation) {
@@ -435,7 +454,7 @@ public class WallpaperCropperTest {
*/
@Test
public void testGetCrop_noSuggestedCrops() {
- setUpWithDisplays(STANDARD_DISPLAY);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY);
Point bitmapSize = new Point(800, 1000);
Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
SparseArray<Rect> suggestedCrops = new SparseArray<>();
@@ -455,8 +474,9 @@ public class WallpaperCropperTest {
for (boolean rtl : List.of(false, true)) {
Rect expectedCrop = rtl ? rightOf(bitmapRect, expectedCropSize)
: leftOf(bitmapRect, expectedCropSize);
- assertThat(mWallpaperCropper.getCrop(
- displaySize, bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(
+ displaySize, defaultDisplayInfo, bitmapSize, suggestedCrops, rtl))
.isEqualTo(expectedCrop);
}
}
@@ -469,7 +489,7 @@ public class WallpaperCropperTest {
*/
@Test
public void testGetCrop_hasSuggestedCrop() {
- setUpWithDisplays(STANDARD_DISPLAY);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY);
Point bitmapSize = new Point(800, 1000);
SparseArray<Rect> suggestedCrops = new SparseArray<>();
suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800));
@@ -479,11 +499,13 @@ public class WallpaperCropperTest {
}
for (boolean rtl : List.of(false, true)) {
- assertThat(mWallpaperCropper.getCrop(
- new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(new Point(300, 800), defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl))
.isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT));
- assertThat(mWallpaperCropper.getCrop(
- new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(new Point(500, 800), defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl))
.isEqualTo(new Rect(0, 0, 500, 800));
}
}
@@ -499,7 +521,7 @@ public class WallpaperCropperTest {
*/
@Test
public void testGetCrop_hasRotatedSuggestedCrop() {
- setUpWithDisplays(STANDARD_DISPLAY);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY);
Point bitmapSize = new Point(2000, 1800);
Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
SparseArray<Rect> suggestedCrops = new SparseArray<>();
@@ -510,12 +532,14 @@ public class WallpaperCropperTest {
suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait));
suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
for (boolean rtl : List.of(false, true)) {
- assertThat(mWallpaperCropper.getCrop(
- landscape, bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(landscape, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl))
.isEqualTo(centerOf(bitmapRect, landscape));
- assertThat(mWallpaperCropper.getCrop(
- squarePortrait, bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(squarePortrait, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl))
.isEqualTo(centerOf(bitmapRect, squarePortrait));
}
}
@@ -532,7 +556,7 @@ public class WallpaperCropperTest {
@Test
public void testGetCrop_hasUnfoldedSuggestedCrop() {
for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
- setUpWithDisplays(displaySizes);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
Point bitmapSize = new Point(2000, 2400);
Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
@@ -569,8 +593,9 @@ public class WallpaperCropperTest {
expectedCrop.right = Math.min(
unfoldedCrop.right, unfoldedCrop.right + maxParallax);
}
- assertThat(mWallpaperCropper.getCrop(
- foldedDisplay, bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(foldedDisplay, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl))
.isEqualTo(expectedCrop);
}
}
@@ -588,7 +613,7 @@ public class WallpaperCropperTest {
@Test
public void testGetCrop_hasFoldedSuggestedCrop() {
for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
- setUpWithDisplays(displaySizes);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
Point bitmapSize = new Point(2000, 2000);
Rect bitmapRect = new Rect(0, 0, 2000, 2000);
@@ -610,12 +635,14 @@ public class WallpaperCropperTest {
Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
for (boolean rtl : List.of(false, true)) {
- assertThat(centerOf(mWallpaperCropper.getCrop(
- unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+ assertThat(centerOf(
+ WallpaperCropper.getCrop(unfoldedDisplayOne, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl), foldedDisplayOne))
.isEqualTo(foldedCropOne);
- assertThat(centerOf(mWallpaperCropper.getCrop(
- unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+ assertThat(centerOf(
+ WallpaperCropper.getCrop(unfoldedDisplayTwo, defaultDisplayInfo, bitmapSize,
+ suggestedCrops, rtl), foldedDisplayTwo))
.isEqualTo(foldedCropTwo);
}
}
@@ -633,7 +660,7 @@ public class WallpaperCropperTest {
@Test
public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
- setUpWithDisplays(displaySizes);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
Point bitmapSize = new Point(2000, 2000);
Rect bitmapRect = new Rect(0, 0, 2000, 2000);
Point largestDisplay = displaySizes.stream().max(
@@ -650,8 +677,9 @@ public class WallpaperCropperTest {
Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
for (boolean rtl : List.of(false, true)) {
- assertThat(mWallpaperCropper.getCrop(
- rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+ assertThat(
+ WallpaperCropper.getCrop(rotatedFoldedDisplay, defaultDisplayInfo,
+ bitmapSize, suggestedCrops, rtl))
.isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
}
}
@@ -670,7 +698,7 @@ public class WallpaperCropperTest {
@Test
public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
- setUpWithDisplays(displaySizes);
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
Point bitmapSize = new Point(2000, 2000);
Rect bitmapRect = new Rect(0, 0, 2000, 2000);
@@ -689,8 +717,8 @@ public class WallpaperCropperTest {
Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
for (boolean rtl : List.of(false, true)) {
- Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
- rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+ Rect rotatedUnfoldedCrop = WallpaperCropper.getCrop(rotatedUnfoldedDisplay,
+ defaultDisplayInfo, bitmapSize, suggestedCrops, rtl);
assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
.isEqualTo(rotatedFoldedCrop);
}
@@ -705,13 +733,13 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(DEFAULT_DISPLAY));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(100, 100));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY,
- wallpaperData)).isTrue();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ DEFAULT_DISPLAY, wallpaperData)).isTrue();
}
// Test isWallpaperCompatibleForDisplay always return true for the stock wallpaper.
@@ -722,49 +750,67 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ true,
new Point(100, 100));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isTrue();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
}
// Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper
// aspect ratio meets the hard-coded aspect ratio.
@Test
- public void isWallpaperCompatibleForDisplay_wallpaperSizeSuitableForDisplayAndMeetAspectRatio_returnTrue()
+ public void isWallpaperCompatibleForDisplay_wallpaperSizeLargerThanDisplayAndMeetAspectRatio_returnTrue()
throws Exception {
final int displayId = 2;
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(4000, 3000));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isTrue();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
}
- // Test isWallpaperCompatibleForDisplay wallpaper is not suitable for the display and wallpaper
- // aspect ratio meets the hard-coded aspect ratio.
+ // Test isWallpaperCompatibleForDisplay wallpaper is smaller than the display but larger than
+ // the threshold and wallpaper aspect ratio meets the hard-coded aspect ratio.
@Test
- public void isWallpaperCompatibleForDisplay_wallpaperSizeNotSuitableForDisplayAndMeetAspectRatio_returnFalse()
+ public void isWallpaperCompatibleForDisplay_wallpaperSizeSmallerThanDisplayButBeyondThresholdAndMeetAspectRatio_returnTrue()
throws Exception {
final int displayId = 2;
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
- new Point(1000, 500));
+ new Point(2000, 900));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isFalse();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
+ }
+
+ // Test isWallpaperCompatibleForDisplay wallpaper is smaller than the display but larger than
+ // the threshold and wallpaper aspect ratio meets the hard-coded aspect ratio.
+ @Test
+ public void isWallpaperCompatibleForDisplay_wallpaperSizeSmallerThanDisplayButAboveThresholdAndMeetAspectRatio_returnFalse()
+ throws Exception {
+ final int displayId = 2;
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 2560;
+ displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
+ doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+ WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+ new Point(2000, 800));
+
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isFalse();
}
// Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper
@@ -776,13 +822,13 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(2000, 4000));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isFalse();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isFalse();
}
// Test isWallpaperCompatibleForDisplay, portrait display, wallpaper is suitable for the display
@@ -794,24 +840,13 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1044;
displayInfo.logicalHeight = 2560;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(2000, 4000));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isTrue();
- }
-
- private void mockDisplay(int displayId, Point displayResolution) {
- final Display mockDisplay = mock(Display.class);
- when(mockDisplay.getDisplayInfo(any(DisplayInfo.class))).thenAnswer(invocation -> {
- DisplayInfo displayInfo = invocation.getArgument(0);
- displayInfo.displayId = displayId;
- displayInfo.logicalWidth = displayResolution.x;
- displayInfo.logicalHeight = displayResolution.y;
- return true;
- });
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
}
private WallpaperData createWallpaperData(boolean isStockWallpaper, Point wallpaperSize)
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java
new file mode 100644
index 000000000000..312db91afb12
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpaper;
+
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.wallpaper.WallpaperDefaultDisplayInfo.FoldableOrientations;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Set;
+
+/** Unit tests for {@link WallpaperDefaultDisplayInfo}. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WallpaperDefaultDisplayInfoTest {
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ }
+
+ @Test
+ public void defaultDisplayInfo_foldable_shouldHaveExpectedContent() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ SparseArray<Point> displaySizes = new SparseArray<>();
+ displaySizes.put(ORIENTATION_PORTRAIT, new Point(1080, 2424));
+ displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2424, 1080));
+ displaySizes.put(ORIENTATION_SQUARE_PORTRAIT, new Point(2076, 2152));
+ displaySizes.put(ORIENTATION_SQUARE_LANDSCAPE, new Point(2152, 2076));
+ assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue();
+ assertThat(defaultDisplayInfo.isFoldable).isTrue();
+ assertThat(defaultDisplayInfo.isLargeScreen).isFalse();
+ assertThat(defaultDisplayInfo.foldableOrientations).containsExactly(
+ new FoldableOrientations(
+ /* foldedOrientation= */ ORIENTATION_PORTRAIT,
+ /* unfoldedOrientation= */ ORIENTATION_SQUARE_PORTRAIT),
+ new FoldableOrientations(
+ /* foldedOrientation= */ ORIENTATION_LANDSCAPE,
+ /* unfoldedOrientation= */ ORIENTATION_SQUARE_LANDSCAPE));
+ }
+
+ @Test
+ public void defaultDisplayInfo_tablet_shouldHaveExpectedContent() {
+ doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect displayBounds = new Rect(0, 0, 2560, 1600);
+ WindowMetrics displayMetrics =
+ new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(displayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ SparseArray<Point> displaySizes = new SparseArray<>();
+ displaySizes.put(ORIENTATION_PORTRAIT, new Point(1600, 2560));
+ displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2560, 1600));
+ assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue();
+ assertThat(defaultDisplayInfo.isFoldable).isFalse();
+ assertThat(defaultDisplayInfo.isLargeScreen).isTrue();
+ assertThat(defaultDisplayInfo.foldableOrientations).isEmpty();
+ }
+
+ @Test
+ public void defaultDisplayInfo_phone_shouldHaveExpectedContent() {
+ doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect displayBounds = new Rect(0, 0, 1280, 2856);
+ WindowMetrics displayMetrics =
+ new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 3f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(displayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ SparseArray<Point> displaySizes = new SparseArray<>();
+ displaySizes.put(ORIENTATION_PORTRAIT, new Point(1280, 2856));
+ displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2856, 1280));
+ assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue();
+ assertThat(defaultDisplayInfo.isFoldable).isFalse();
+ assertThat(defaultDisplayInfo.isLargeScreen).isFalse();
+ assertThat(defaultDisplayInfo.foldableOrientations).isEmpty();
+ }
+
+ @Test
+ public void defaultDisplayInfo_equals_sameContent_shouldEqual() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+ WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo).isEqualTo(otherDefaultDisplayInfo);
+ }
+
+ @Test
+ public void defaultDisplayInfo_equals_differentBounds_shouldNotEqual() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ // For the first call
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics))
+ // For the second+ call
+ .thenReturn(Set.of(innerDisplayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+ WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo).isNotEqualTo(otherDefaultDisplayInfo);
+ }
+
+ @Test
+ public void defaultDisplayInfo_hashCode_sameContent_shouldEqual() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+ WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo.hashCode()).isEqualTo(otherDefaultDisplayInfo.hashCode());
+ }
+
+ @Test
+ public void defaultDisplayInfo_hashCode_differentBounds_shouldNotEqual() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ // For the first call
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics))
+ // For the second+ call
+ .thenReturn(Set.of(innerDisplayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+ WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo.hashCode()).isNotEqualTo(otherDefaultDisplayInfo.hashCode());
+ }
+
+ @Test
+ public void getFoldedOrientation_foldable_shouldReturnExpectedOrientation() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+ .isEqualTo(ORIENTATION_PORTRAIT);
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+ .isEqualTo(ORIENTATION_LANDSCAPE);
+ // Use a folded orientation for a folded orientation should return unknown.
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ }
+
+ @Test
+ public void getUnfoldedOrientation_foldable_shouldReturnExpectedOrientation() {
+ doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+ Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+ WindowMetrics innerDisplayMetrics =
+ new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ WindowMetrics outerDisplayMetrics =
+ new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2.4375f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT))
+ .isEqualTo(ORIENTATION_SQUARE_PORTRAIT);
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE))
+ .isEqualTo(ORIENTATION_SQUARE_LANDSCAPE);
+ // Use an unfolded orientation for an unfolded orientation should return unknown.
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ }
+
+ @Test
+ public void getFoldedOrientation_nonFoldable_shouldReturnUnknown() {
+ doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect displayBounds = new Rect(0, 0, 2560, 1600);
+ WindowMetrics displayMetrics =
+ new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(displayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ }
+
+ @Test
+ public void getUnFoldedOrientation_nonFoldable_shouldReturnUnknown() {
+ doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+ Rect displayBounds = new Rect(0, 0, 2560, 1600);
+ WindowMetrics displayMetrics =
+ new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+ /* density= */ 2f);
+ when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+ .thenReturn(Set.of(displayMetrics));
+
+ WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+ mWindowManager, mResources);
+
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE))
+ .isEqualTo(ORIENTATION_UNKNOWN);
+ }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 4e56422ec391..94ce72368a92 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -39,9 +39,9 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.content.Context;
@@ -53,8 +53,8 @@ import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IWakeLockCallback;
import android.os.IScreenTimeoutPolicyListener;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -205,7 +205,7 @@ public class NotifierTest {
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device doesn't vibrate
- verifyZeroInteractions(mVibrator);
+ verifyNoMoreInteractions(mVibrator);
}
@Test
@@ -238,7 +238,7 @@ public class NotifierTest {
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device doesn't vibrate
- verifyZeroInteractions(mVibrator);
+ verifyNoMoreInteractions(mVibrator);
}
@Test
@@ -725,10 +725,11 @@ public class NotifierTest {
final int uid = 1234;
final int pid = 5678;
+
mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
"my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
exceptingCallback);
- verifyZeroInteractions(mWakeLockLog);
+ verifyNoMoreInteractions(mWakeLockLog);
mTestLooper.dispatchAll();
verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
clearInvocations(mBatteryStats);
@@ -790,6 +791,55 @@ public class NotifierTest {
}
@Test
+ public void test_wakeLockLogUsesWorkSource() {
+ createNotifier();
+ clearInvocations(mWakeLockLog);
+ IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+ @Override public void onStateChanged(boolean enabled) throws RemoteException {
+ throw new RemoteException("Just testing");
+ }
+ };
+
+ final int uid = 1234;
+ final int pid = 5678;
+ WorkSource worksource = new WorkSource(1212);
+ WorkSource worksource2 = new WorkSource(3131);
+
+ mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, worksource, /* historyTag= */ null,
+ exceptingCallback);
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", 1212,
+ PowerManager.PARTIAL_WAKE_LOCK, -1);
+
+ // Release the wakelock
+ mNotifier.onWakeLockReleased(PowerManager.FULL_WAKE_LOCK, "wakelockTag2",
+ "my.package.name", uid, pid, worksource2, /* historyTag= */ null,
+ exceptingCallback);
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag2", 3131, -1);
+
+ // clear the handler
+ mTestLooper.dispatchAll();
+
+ // Now test with improveWakelockLatency flag true
+ clearInvocations(mWakeLockLog);
+ when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
+
+ mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, worksource, /* historyTag= */ null,
+ exceptingCallback);
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", 1212,
+ PowerManager.PARTIAL_WAKE_LOCK, 1);
+
+ // Release the wakelock
+ mNotifier.onWakeLockReleased(PowerManager.FULL_WAKE_LOCK, "wakelockTag2",
+ "my.package.name", uid, pid, worksource2, /* historyTag= */ null,
+ exceptingCallback);
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag2", 3131, 1);
+ }
+
+ @Test
public void
test_notifierProcessesWorkSourceDeepCopy_OnWakelockChanging() throws RemoteException {
when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
@@ -845,7 +895,7 @@ public class NotifierTest {
exceptingCallback);
// No interaction because we expect that to happen in async
- verifyZeroInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
+ verifyNoMoreInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
// Progressing the looper, and validating all the interactions
mTestLooper.dispatchAll();
@@ -944,15 +994,23 @@ public class NotifierTest {
assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK),
PowerManager.PARTIAL_WAKE_LOCK);
assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+ PowerManager.DOZE_WAKE_LOCK), -1);
+ }
+
+ @Test
+ public void getWakelockMonitorTypeForLogging_evaluateProximityLevel() {
+ // How proximity wakelock is evaluated depends on boolean configuration. Test both.
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity))
+ .thenReturn(false);
+ createNotifier();
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK),
PowerManager.PARTIAL_WAKE_LOCK);
- assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
- PowerManager.DOZE_WAKE_LOCK), -1);
when(mResourcesSpy.getBoolean(
com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity))
.thenReturn(true);
-
createNotifier();
assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1);
@@ -1239,7 +1297,7 @@ public class NotifierTest {
}
@Override
- public WakeLockLog getWakeLockLog(Context context) {
+ public @NonNull WakeLockLog getWakeLockLog(Context context) {
return mWakeLockLog;
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
index c1d7c7b4a4c2..534337ee9dbf 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
@@ -27,6 +27,8 @@ import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.os.Process;
+import com.android.server.power.WakeLockLog.TagData;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -62,8 +64,9 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItems_withNoEventTimeSupplied() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
log.onWakeLockAcquired("TagPartial", 101,
@@ -93,8 +96,9 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItems() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -117,8 +121,9 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItemsWithTimeReset() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
@@ -136,9 +141,10 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItemsWithTagOverwrite() {
- final int tagDatabaseSize = 2;
+ final int tagDatabaseSize = 1;
+ final int tagStartingSize = 1;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
@@ -157,8 +163,9 @@ public class WakeLockLogTest {
@Test
public void testAddFourItemsWithRingBufferOverflow() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
// Wake lock 1 acquired - log size = 3
@@ -206,8 +213,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithBadTag() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
// Bad tag means it wont get written
@@ -224,8 +232,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithReducedTagName() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101,
@@ -242,9 +251,10 @@ public class WakeLockLogTest {
@Test
public void testAddAcquireAndReleaseWithRepeatTagName() {
- final int tagDatabaseSize = 6;
+ final int tagDatabaseSize = 5;
+ final int tagStartingSize = 5;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
@@ -263,8 +273,9 @@ public class WakeLockLogTest {
@Test
public void testAddAcquireAndReleaseWithTimeTravel() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1100L);
@@ -283,8 +294,9 @@ public class WakeLockLogTest {
@Test
public void testAddSystemWakelock() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -302,8 +314,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithNoPackageName() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
when(mPackageManager.getPackagesForUid(101)).thenReturn(null);
@@ -322,8 +335,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithMultiplePackageNames() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
when(mPackageManager.getPackagesForUid(101)).thenReturn(
@@ -344,8 +358,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemsWithRepeatOwnerUid_UsesCache() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -375,8 +390,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemsWithRepeatOwnerUid_SavedAcquisitions_UsesCache() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -420,6 +436,34 @@ public class WakeLockLogTest {
verify(mPackageManager, times(1)).getPackagesForUid(101);
}
+ @Test
+ public void testTagDatabaseGrowsBeyondStartingSize() {
+ final int tagDatabaseSize = 3;
+ final int tagStartingSize = 1;
+ final int logSize = 10;
+ // start with size = 1 and max size
+ TestInjector injector = new TestInjector(tagDatabaseSize, tagStartingSize, logSize);
+ WakeLockLog.TagDatabase td = new WakeLockLog.TagDatabase(injector);
+
+ // Add one
+ TagData data1 = td.findOrCreateTag("Tagname1", 1001, /* shouldCreate= */ true);
+ assertEquals(0, td.getTagIndex(data1));
+
+ // Check that it grows by adding 1 more
+ TagData data2 = td.findOrCreateTag("Tagname2", 1001, /* shouldCreate= */ true);
+ assertEquals(1, td.getTagIndex(data2));
+
+ // Lets add the last one to fill up the DB to maxSize
+ TagData data3 = td.findOrCreateTag("Tagname3", 1001, /* shouldCreate= */ true);
+ assertEquals(2, td.getTagIndex(data3));
+
+ // Adding a fourth one should replace the oldest one (Tagname1)
+ TagData data4 = td.findOrCreateTag("Tagname4", 1001, /* shouldCreate= */ true);
+ assertEquals(0, td.getTagIndex(data4));
+ assertEquals(tagDatabaseSize, td.getTagIndex(data1));
+
+ }
+
private String dumpLog(WakeLockLog log, boolean includeTagDb) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
@@ -429,10 +473,12 @@ public class WakeLockLogTest {
public static class TestInjector extends WakeLockLog.Injector {
private final int mTagDatabaseSize;
+ private final int mTagStartingSize;
private final int mLogSize;
- public TestInjector(int tagDatabaseSize, int logSize) {
+ public TestInjector(int tagDatabaseSize, int tagStartingSize, int logSize) {
mTagDatabaseSize = tagDatabaseSize;
+ mTagStartingSize = tagStartingSize;
mLogSize = logSize;
}
@@ -442,6 +488,11 @@ public class WakeLockLogTest {
}
@Override
+ public int getTagDatabaseStartingSize() {
+ return mTagStartingSize;
+ }
+
+ @Override
public int getLogSize() {
return mLogSize;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 5165e34c7fcd..fc864dd230d9 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
@@ -330,31 +331,24 @@ public class BatteryStatsHistoryTest {
return invocation.callRealMethod();
}).when(mHistory).readFragmentToParcel(any(), any());
- // Prepare history for iteration
- mHistory.iterate(0, MonotonicClock.UNDEFINED);
-
- Parcel parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
- assertThat(parcel).isNotNull();
- assertThat(mReadFiles).containsExactly("123.bh");
-
- // Skip to the end to force reading the next parcel
- parcel.setDataPosition(parcel.dataSize());
- mReadFiles.clear();
- parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
- assertThat(parcel).isNotNull();
- assertThat(mReadFiles).containsExactly("1000.bh");
-
- parcel.setDataPosition(parcel.dataSize());
- mReadFiles.clear();
- parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
- assertThat(parcel).isNotNull();
- assertThat(mReadFiles).containsExactly("2000.bh");
+ int eventsRead = 0;
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, MonotonicClock.UNDEFINED);
+ while (iterator.hasNext()) {
+ HistoryItem item = iterator.next();
+ if (item.eventCode == HistoryItem.EVENT_JOB_START) {
+ eventsRead++;
+ assertThat(mReadFiles).containsExactly("123.bh");
+ } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) {
+ eventsRead++;
+ assertThat(mReadFiles).containsExactly("123.bh", "1000.bh");
+ } else if (item.eventCode == HistoryItem.EVENT_ALARM) {
+ eventsRead++;
+ assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh");
+ }
+ }
- parcel.setDataPosition(parcel.dataSize());
- mReadFiles.clear();
- parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
- assertThat(parcel).isNull();
- assertThat(mReadFiles).isEmpty();
+ assertThat(eventsRead).isEqualTo(3);
+ assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh", "3000.bh");
}
@Test
@@ -372,25 +366,19 @@ public class BatteryStatsHistoryTest {
return invocation.callRealMethod();
}).when(mHistory).readFragmentToParcel(any(), any());
- // Prepare history for iteration
- mHistory.iterate(1000, 3000);
-
- Parcel parcel = mHistory.getNextParcel(1000, 3000);
- assertThat(parcel).isNotNull();
- assertThat(mReadFiles).containsExactly("1000.bh");
-
- // Skip to the end to force reading the next parcel
- parcel.setDataPosition(parcel.dataSize());
- mReadFiles.clear();
- parcel = mHistory.getNextParcel(1000, 3000);
- assertThat(parcel).isNotNull();
- assertThat(mReadFiles).containsExactly("2000.bh");
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(1000, 3000);
+ while (iterator.hasNext()) {
+ HistoryItem item = iterator.next();
+ if (item.eventCode == HistoryItem.EVENT_JOB_START) {
+ fail("Event outside the range");
+ } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) {
+ assertThat(mReadFiles).containsExactly("1000.bh");
+ } else if (item.eventCode == HistoryItem.EVENT_ALARM) {
+ fail("Event outside the range");
+ }
+ }
- parcel.setDataPosition(parcel.dataSize());
- mReadFiles.clear();
- parcel = mHistory.getNextParcel(1000, 3000);
- assertThat(parcel).isNull();
- assertThat(mReadFiles).isEmpty();
+ assertThat(mReadFiles).containsExactly("1000.bh", "2000.bh");
}
private void prepareMultiFileHistory() {
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index 879aa4893802..2fd316edf71a 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -409,7 +409,7 @@ public class IntrusionDetectionServiceTest {
final String TAG = "startTestService";
final CountDownLatch latch = new CountDownLatch(1);
LocalIntrusionDetectionEventTransport transport =
- new LocalIntrusionDetectionEventTransport();
+ new LocalIntrusionDetectionEventTransport(mContext);
ServiceConnection serviceConnection = new ServiceConnection() {
// Called when connection with the service is established.
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
index ca5952b140c1..e4381007edc0 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
@@ -34,7 +34,6 @@ android_test_helper_app {
srcs: ["**/*.java"],
platform_apis: true,
- certificate: "platform",
dxflags: ["--multi-dex"],
optimize: {
enabled: false,
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
index f0012da44fa4..b0b781575cb3 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
@@ -18,8 +18,13 @@
package com.android.coretests.apps.testapp;
+import android.app.admin.SecurityLog;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+import android.content.Intent;
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.security.intrusiondetection.IntrusionDetectionEventTransport;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -36,6 +41,44 @@ import java.util.List;
public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEventTransport {
private List<IntrusionDetectionEvent> mEvents = new ArrayList<>();
+ private static final String ACTION_SECURITY_EVENT_RECEIVED =
+ "com.android.coretests.apps.testapp.ACTION_SECURITY_EVENT_RECEIVED";
+ private static final String TAG = "LocalIntrusionDetectionEventTransport";
+ private static final String TEST_SECURITY_EVENT_TAG = "test_security_event_tag";
+ private static Context sContext;
+
+ public LocalIntrusionDetectionEventTransport(Context context) {
+ sContext = context;
+ }
+
+ // Broadcast an intent to the CTS test service to indicate that the security
+ // event was received.
+ private static void broadcastSecurityEventReceived() {
+ try {
+ Intent intent = new Intent(ACTION_SECURITY_EVENT_RECEIVED);
+ sContext.sendBroadcast(intent);
+ Log.i(TAG, "LIZ_TESTING: sent broadcast");
+ } catch (Exception e) {
+ Log.e(TAG, "Exception sending broadcast", e);
+ }
+ }
+
+ private static void checkIfSecurityEventReceivedFromCts(List<IntrusionDetectionEvent> events) {
+ // Loop through the events and check if any of them are the security event
+ // that uses the TEST_SECURITY_EVENT_TAG tag, which is set by the CTS test.
+ for (IntrusionDetectionEvent event : events) {
+ if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
+ SecurityEvent securityEvent = event.getSecurityEvent();
+ Object[] eventData = (Object[]) securityEvent.getData();
+ if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
+ && eventData[1].equals(TEST_SECURITY_EVENT_TAG)) {
+ broadcastSecurityEventReceived();
+ return;
+ }
+ }
+ }
+ }
+
@Override
public boolean initialize() {
return true;
@@ -43,6 +86,11 @@ public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEve
@Override
public boolean addData(List<IntrusionDetectionEvent> events) {
+ // Our CTS tests will generate a security event. In order to
+ // verify the event is received with the appropriate data, we will
+ // check the events locally and set a property value that can be
+ // read by the test.
+ checkIfSecurityEventReceivedFromCts(events);
mEvents.addAll(events);
return true;
}
@@ -55,4 +103,4 @@ public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEve
public List<IntrusionDetectionEvent> getEvents() {
return mEvents;
}
-} \ No newline at end of file
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
index e4bf987402fd..9183a75580ff 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
@@ -17,19 +17,20 @@
package com.android.coretests.apps.testapp;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
-import android.os.Process;
-
-import com.android.internal.infra.AndroidFuture;
-
public class TestLoggingService extends Service {
private static final String TAG = "TestLoggingService";
private LocalIntrusionDetectionEventTransport mLocalIntrusionDetectionEventTransport;
- public TestLoggingService() {
- mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport();
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Context context = getApplicationContext();
+ mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport(context);
}
// Binder given to clients.
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 5240f581fd9f..cc0d5e4710d2 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -32,8 +32,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index ce3751abfed7..d254e9689048 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -23,10 +23,10 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyListOf;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@@ -35,7 +35,6 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.Manifest.permission;
@@ -243,7 +242,7 @@ public class NetworkScoreServiceTest {
@Test
public void testRequestScores_providerNotConnected() throws Exception {
assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
- verifyZeroInteractions(mRecommendationProvider);
+ verifyNoMoreInteractions(mRecommendationProvider);
}
@Test
@@ -328,8 +327,8 @@ public class NetworkScoreServiceTest {
// updateScores should update both caches
mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
- verify(mNetworkScoreCache).updateScores(anyListOf(ScoredNetwork.class));
- verify(mNetworkScoreCache2).updateScores(anyListOf(ScoredNetwork.class));
+ verify(mNetworkScoreCache).updateScores(anyList());
+ verify(mNetworkScoreCache2).updateScores(anyList());
mNetworkScoreService.unregisterNetworkScoreCache(
NetworkKey.TYPE_WIFI, mNetworkScoreCache2);
@@ -337,7 +336,7 @@ public class NetworkScoreServiceTest {
// updateScores should only update the first cache since the 2nd has been unregistered
mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
- verify(mNetworkScoreCache, times(2)).updateScores(anyListOf(ScoredNetwork.class));
+ verify(mNetworkScoreCache, times(2)).updateScores(anyList());
mNetworkScoreService.unregisterNetworkScoreCache(
NetworkKey.TYPE_WIFI, mNetworkScoreCache);
@@ -604,7 +603,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, null /*cookie*/);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -618,7 +617,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -632,7 +631,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, -1 /*cookie*/);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -646,7 +645,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
- verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -658,7 +657,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
- verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
+ verifyNoMoreInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
}
@Test
@@ -676,7 +675,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
verify(mNetworkScoreCache).updateScores(filteredList);
- verifyZeroInteractions(mScanResultsFilter);
+ verifyNoMoreInteractions(mScanResultsFilter);
}
@Test
@@ -694,7 +693,7 @@ public class NetworkScoreServiceTest {
consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
verify(mNetworkScoreCache).updateScores(filteredList);
- verifyZeroInteractions(mCurrentNetworkFilter);
+ verifyNoMoreInteractions(mCurrentNetworkFilter);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
index 52428e8bc478..1621c5d41a3d 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
@@ -23,9 +23,9 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index c18faef2c028..16df97b76c94 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -18,7 +18,7 @@ package com.android.server;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 42b84bdc51e6..c7c8c5846bb1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -64,7 +64,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityService;
@@ -838,7 +837,7 @@ public class AbstractAccessibilityServiceConnectionTest {
// ...without secure layers included
assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isFalse();
// No error sent to callback
- verifyZeroInteractions(mMockCallback);
+ verifyNoMoreInteractions(mMockCallback);
}
@Test
@@ -856,7 +855,7 @@ public class AbstractAccessibilityServiceConnectionTest {
// ...with secure layers included
assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue();
// No error sent to callback
- verifyZeroInteractions(mMockCallback);
+ verifyNoMoreInteractions(mMockCallback);
}
@Test
@@ -889,7 +888,7 @@ public class AbstractAccessibilityServiceConnectionTest {
// ...with secure layers included
assertThat(layerArgsCaptor.getValue().mCaptureSecureLayers).isTrue();
// No error sent to callback
- verifyZeroInteractions(mMockCallback);
+ verifyNoMoreInteractions(mMockCallback);
}
private void takeScreenshotOfWindow(int windowFlags) throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 9cfa51a85988..2ccd33648c3e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -50,6 +50,7 @@ import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
@@ -66,6 +67,7 @@ import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -77,10 +79,12 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.input.KeyGestureEvent;
import android.net.Uri;
@@ -114,6 +118,7 @@ import android.view.accessibility.IUserInitializationCompleteCallback;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
@@ -136,6 +141,8 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.google.common.truth.Correspondence;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -188,6 +195,8 @@ public class AccessibilityManagerServiceTest {
DESCRIPTION,
TEST_PENDING_INTENT);
+ private static final int FAKE_SYSTEMUI_UID = 1000;
+
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
private static final String TARGET_MAGNIFICATION = MAGNIFICATION_CONTROLLER_NAME;
private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE =
@@ -207,11 +216,12 @@ public class AccessibilityManagerServiceTest {
@Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
@Mock private PackageManager mMockPackageManager;
+ @Mock
+ private PackageManagerInternal mMockPackageManagerInternal;
@Mock private WindowManagerInternal mMockWindowManagerService;
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@Mock private SystemActionPerformer mMockSystemActionPerformer;
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
- @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
@Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
@Mock private UserManagerInternal mMockUserManagerInternal;
@Mock private IBinder mMockBinder;
@@ -234,6 +244,7 @@ public class AccessibilityManagerServiceTest {
private TestableLooper mTestableLooper;
private Handler mHandler;
private FakePermissionEnforcer mFakePermissionEnforcer;
+ private TestDisplayManagerWrapper mTestDisplayManagerWrapper;
@Before
public void setUp() throws Exception {
@@ -246,6 +257,7 @@ public class AccessibilityManagerServiceTest {
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
LocalServices.removeServiceForTest(PermissionEnforcer.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
@@ -256,6 +268,12 @@ public class AccessibilityManagerServiceTest {
mInputFilter = mock(FakeInputFilter.class);
mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+ when(mMockPackageManagerInternal.getSystemUiServiceComponent()).thenReturn(
+ new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService"));
+ when(mMockPackageManagerInternal.getPackageUid(eq("com.android.systemui"), anyLong(),
+ anyInt())).thenReturn(FAKE_SYSTEMUI_UID);
+ LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+
when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
mMockMagnificationConnectionManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
@@ -273,15 +291,9 @@ public class AccessibilityManagerServiceTest {
eq(UserHandle.USER_CURRENT)))
.thenReturn(mTestableContext.getUserId());
- final ArrayList<Display> displays = new ArrayList<>();
- final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(),
- Display.DEFAULT_DISPLAY, new DisplayInfo(),
- DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
- final Display testDisplay = new Display(DisplayManagerGlobal.getInstance(), TEST_DISPLAY,
- new DisplayInfo(), DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
- displays.add(defaultDisplay);
- displays.add(testDisplay);
- when(mMockA11yDisplayListener.getValidDisplayList()).thenReturn(displays);
+ mTestDisplayManagerWrapper = new TestDisplayManagerWrapper(mTestableContext);
+ mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL);
mA11yms = new AccessibilityManagerService(
mTestableContext,
@@ -290,7 +302,7 @@ public class AccessibilityManagerServiceTest {
mMockSecurityPolicy,
mMockSystemActionPerformer,
mMockA11yWindowManager,
- mMockA11yDisplayListener,
+ mTestDisplayManagerWrapper,
mMockMagnificationController,
mInputFilter,
mProxyManager,
@@ -2309,6 +2321,73 @@ public class AccessibilityManagerServiceTest {
mA11yms.getCurrentUserIdLocked())).isEmpty();
}
+ @Test
+ public void displayListReturnsDisplays() {
+ mTestDisplayManagerWrapper.mDisplays = createFakeDisplayList(
+ Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_WIFI,
+ Display.TYPE_OVERLAY,
+ Display.TYPE_VIRTUAL
+ );
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // In #setUp() we already have TYPE_INTERNAL and TYPE_EXTERNAL. Call the rest.
+ for (int i = 2; i < mTestDisplayManagerWrapper.mDisplays.size(); i++) {
+ mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(
+ mTestDisplayManagerWrapper.mDisplays.get(i).getDisplayId());
+ }
+ });
+
+ List<Display> displays = mA11yms.getValidDisplayList();
+ assertThat(displays).hasSize(5);
+ assertThat(displays)
+ .comparingElementsUsing(
+ Correspondence.transforming(Display::getType, "has a type of"))
+ .containsExactly(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_WIFI,
+ Display.TYPE_OVERLAY,
+ Display.TYPE_VIRTUAL);
+ }
+
+ @Test
+ public void displayListReturnsDisplays_excludesVirtualPrivate() {
+ // Add a private virtual display whose uid is different from systemui.
+ final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL);
+ displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID + 100));
+ mTestDisplayManagerWrapper.mDisplays = displays;
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2);
+ });
+
+ List<Display> validDisplays = mA11yms.getValidDisplayList();
+ assertThat(validDisplays).hasSize(2);
+ assertThat(validDisplays)
+ .comparingElementsUsing(
+ Correspondence.transforming(Display::getType, "has a type of"))
+ .doesNotContain(Display.TYPE_VIRTUAL);
+ }
+
+ @Test
+ public void displayListReturnsDisplays_includesVirtualSystemUIPrivate() {
+ // Add a private virtual display whose uid is systemui.
+ final List<Display> displays = createFakeDisplayList(Display.TYPE_INTERNAL,
+ Display.TYPE_EXTERNAL);
+ displays.add(createFakeVirtualPrivateDisplay(/* displayId= */ 2, FAKE_SYSTEMUI_UID));
+ mTestDisplayManagerWrapper.mDisplays = displays;
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mTestDisplayManagerWrapper.mRegisteredListener.onDisplayAdded(2);
+ });
+
+ List<Display> validDisplays = mA11yms.getValidDisplayList();
+ assertThat(validDisplays).hasSize(3);
+ assertThat(validDisplays)
+ .comparingElementsUsing(
+ Correspondence.transforming(Display::getType, "has a type of"))
+ .contains(Display.TYPE_VIRTUAL);
+ }
+
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(
@@ -2374,14 +2453,6 @@ public class AccessibilityManagerServiceTest {
return lockState;
}
- private void assertStartActivityWithExpectedComponentName(Context mockContext,
- String componentName) {
- verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
- any(Bundle.class), any(UserHandle.class));
- assertThat(mIntentArgumentCaptor.getValue().getStringExtra(
- Intent.EXTRA_COMPONENT_NAME)).isEqualTo(componentName);
- }
-
private void assertStartActivityWithExpectedShortcutType(Context mockContext,
@UserShortcutType int shortcutType) {
verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
@@ -2430,6 +2501,27 @@ public class AccessibilityManagerServiceTest {
});
}
+ private static List<Display> createFakeDisplayList(int... types) {
+ final ArrayList<Display> displays = new ArrayList<>();
+ for (int i = 0; i < types.length; i++) {
+ final DisplayInfo info = new DisplayInfo();
+ info.type = types[i];
+ final Display display = new Display(DisplayManagerGlobal.getInstance(),
+ i, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ displays.add(display);
+ }
+ return displays;
+ }
+
+ private static Display createFakeVirtualPrivateDisplay(int displayId, int uid) {
+ final DisplayInfo info = new DisplayInfo();
+ info.type = Display.TYPE_VIRTUAL;
+ info.flags |= Display.FLAG_PRIVATE;
+ info.ownerUid = uid;
+ return new Display(DisplayManagerGlobal.getInstance(),
+ displayId, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ }
+
public static class FakeInputFilter extends AccessibilityInputFilter {
FakeInputFilter(Context context,
AccessibilityManagerService service) {
@@ -2484,10 +2576,6 @@ public class AccessibilityManagerServiceTest {
return mMockContext;
}
- public void addMockUserContext(int userId, Context context) {
- mMockUserContexts.put(userId, context);
- }
-
@Override
@NonNull
public Context createContextAsUser(UserHandle user, int flags) {
@@ -2518,4 +2606,35 @@ public class AccessibilityManagerServiceTest {
Set<String> setting = readStringsFromSetting(ShortcutUtils.convertToKey(shortcutType));
assertThat(setting).containsExactlyElementsIn(value);
}
+
+ private static class TestDisplayManagerWrapper extends
+ AccessibilityDisplayListener.DisplayManagerWrapper {
+ List<Display> mDisplays;
+ DisplayManager.DisplayListener mRegisteredListener;
+
+ TestDisplayManagerWrapper(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Display[] getDisplays() {
+ return mDisplays.toArray(new Display[0]);
+ }
+
+ @Override
+ public Display getDisplay(int displayId) {
+ for (final Display display : mDisplays) {
+ if (display.getDisplayId() == displayId) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+ @Nullable Handler handler) {
+ mRegisteredListener = listener;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
index c16f3d3fa1ab..1792201a304a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
@@ -29,9 +29,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -129,7 +129,7 @@ public class ActionReplacingCallbackTest {
mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID,
INTERROGATING_TID);
verify(mMockReplacerConnection).findAccessibilityNodeInfoByAccessibilityId(
- eq(AccessibilityNodeInfo.ROOT_NODE_ID), (Region) anyObject(),
+ eq(AccessibilityNodeInfo.ROOT_NODE_ID), (Region) any(),
mInteractionIdCaptor.capture(), eq(mActionReplacingCallback), eq(0),
eq(INTERROGATING_PID), eq(INTERROGATING_TID), nullable(MagnificationSpec.class),
nullable(float[].class), eq(null));
@@ -246,9 +246,9 @@ public class ActionReplacingCallbackTest {
throws RemoteException {
doThrow(RemoteException.class).when(mMockReplacerConnection)
.findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID),
- (Region) anyObject(), anyInt(), (ActionReplacingCallback) anyObject(),
+ (Region) any(), anyInt(), (ActionReplacingCallback) any(),
eq(0), eq(INTERROGATING_PID), eq(INTERROGATING_TID),
- (MagnificationSpec) anyObject(), nullable(float[].class), eq(null));
+ (MagnificationSpec) any(), nullable(float[].class), eq(null));
ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback(
mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID,
INTERROGATING_TID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
index 96ae102e53f3..d0dc2cbb0f86 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.FingerprintGestureController;
@@ -86,7 +86,7 @@ public class FingerprintGestureControllerTest {
mMockFingerprintGestureCallback);
mFingerprintGestureController.onGestureDetectionActiveChanged(true);
mFingerprintGestureController.onGestureDetectionActiveChanged(false);
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
}
@Test
@@ -118,7 +118,7 @@ public class FingerprintGestureControllerTest {
mFingerprintGestureController.onGestureDetectionActiveChanged(true);
mFingerprintGestureController.onGestureDetectionActiveChanged(false);
assertFalse(messageCapturingHandler.hasMessages());
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
messageCapturingHandler.removeAllMessages();
}
@@ -135,7 +135,7 @@ public class FingerprintGestureControllerTest {
mFingerprintGestureController.unregisterFingerprintGestureCallback(
mMockFingerprintGestureCallback);
mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
}
@Test
@@ -159,7 +159,7 @@ public class FingerprintGestureControllerTest {
mMockFingerprintGestureCallback);
mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
assertFalse(messageCapturingHandler.hasMessages());
- verifyZeroInteractions(mMockFingerprintGestureCallback);
+ verifyNoMoreInteractions(mMockFingerprintGestureCallback);
messageCapturingHandler.removeAllMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
index 30d00ad716e6..f5a708d71ba3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
@@ -18,8 +18,8 @@ package com.android.server.accessibility;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
index efea21428937..3565244d90b3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
@@ -36,6 +36,8 @@ import android.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
import android.media.AudioManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
@@ -67,6 +69,8 @@ import java.util.concurrent.Executor;
public class HearingDevicePhoneCallNotificationControllerTest {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
@@ -118,6 +122,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
AudioManager.DEVICE_OUT_BLE_HEADSET);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(hapDeviceInfo);
when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
@@ -132,6 +137,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(a2dpDeviceInfo));
mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
@@ -146,6 +152,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
AudioManager.DEVICE_OUT_BLE_HEADSET);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(hapDeviceInfo);
when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
@@ -155,6 +162,51 @@ public class HearingDevicePhoneCallNotificationControllerTest {
eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH));
}
+ @Test
+ @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE)
+ public void onCallStateChanged_nonHearingDevice_offHookThenIdle_callAddAndRemoveListener() {
+ final ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener> listenerCaptor =
+ ArgumentCaptor.forClass(AudioManager.OnCommunicationDeviceChangedListener.class);
+ AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE);
+
+ verify(mAudioManager).addOnCommunicationDeviceChangedListener(any(Executor.class),
+ listenerCaptor.capture());
+ verify(mAudioManager).removeOnCommunicationDeviceChangedListener(
+ eq(listenerCaptor.getValue()));
+ }
+
+
+ @Test
+ @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE)
+ public void onCallStateChanged_hearingDeviceFromCommunicationDeviceChanged_showNotification() {
+ final ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener> listenerCaptor =
+ ArgumentCaptor.forClass(AudioManager.OnCommunicationDeviceChangedListener.class);
+ AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+ AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+ verify(mAudioManager).addOnCommunicationDeviceChangedListener(any(Executor.class),
+ listenerCaptor.capture());
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{hapDeviceInfo});
+ listenerCaptor.getValue().onCommunicationDeviceChanged(hapDeviceInfo);
+
+ verify(mNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
+ }
+
private AudioDeviceInfo createAudioDeviceInfo(String address, int type) {
AudioDevicePort audioDevicePort = mock(AudioDevicePort.class);
doReturn(type).when(audioDevicePort).type();
@@ -171,7 +223,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
HearingDevicePhoneCallNotificationController.CallStateListener {
TestCallStateListener(@NonNull Context context) {
- super(context);
+ super(context, context.getMainExecutor());
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
index ebb73e877db4..186f7425b189 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
@@ -20,14 +20,14 @@ import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
@@ -96,12 +96,12 @@ public class KeyEventDispatcherTest {
mLock, powerManager, mMessageCapturingHandler);
mKeyEventFilter1 = mock(KeyEventFilter.class);
- when(mKeyEventFilter1.onKeyEvent((KeyEvent) anyObject(),
+ when(mKeyEventFilter1.onKeyEvent((KeyEvent) any(),
mFilter1SequenceCaptor.capture().intValue()))
.thenReturn(true);
mKeyEventFilter2 = mock(KeyEventFilter.class);
- when(mKeyEventFilter2.onKeyEvent((KeyEvent) anyObject(),
+ when(mKeyEventFilter2.onKeyEvent((KeyEvent) any(),
mFilter2SequenceCaptor.capture().intValue()))
.thenReturn(true);
}
@@ -122,7 +122,7 @@ public class KeyEventDispatcherTest {
@Test
public void testNotifyKeyEvent_boundServiceDoesntProcessEvents_shouldReturnFalse() {
KeyEventFilter keyEventFilter = mock(KeyEventFilter.class);
- when(keyEventFilter.onKeyEvent((KeyEvent) anyObject(), anyInt())).thenReturn(false);
+ when(keyEventFilter.onKeyEvent((KeyEvent) any(), anyInt())).thenReturn(false);
assertFalse(mKeyEventDispatcher
.notifyKeyEventLocked(mKeyEvent, 0, Arrays.asList(keyEventFilter)));
assertFalse(isTimeoutPending(mMessageCapturingHandler));
@@ -159,7 +159,7 @@ public class KeyEventDispatcherTest {
mFilter1SequenceCaptor.getValue());
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
assertFalse(isTimeoutPending(mMessageCapturingHandler));
}
@@ -189,7 +189,7 @@ public class KeyEventDispatcherTest {
mFilter2SequenceCaptor.getValue());
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
assertFalse(isTimeoutPending(mMessageCapturingHandler));
}
@@ -261,7 +261,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.setOnKeyEventResult(mKeyEventFilter2, false,
mFilter2SequenceCaptor.getValue());
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
assertFalse(isTimeoutPending(mMessageCapturingHandler));
}
@@ -278,7 +278,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0));
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
@@ -293,7 +293,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0));
assertTrue(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
@@ -327,7 +327,7 @@ public class KeyEventDispatcherTest {
mFilter1SequenceCaptor.getValue());
assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
@@ -344,7 +344,7 @@ public class KeyEventDispatcherTest {
mKeyEventDispatcher.handleMessage(getTimedMessage(mMessageCapturingHandler, 0));
assertFalse(mInputEventsHandler.hasMessages(SEND_FRAMEWORK_KEY_EVENT));
- verifyZeroInteractions(mMockPowerManagerService);
+ verifyNoMoreInteractions(mMockPowerManagerService);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index c62cae5e9b6e..e3515125397a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -21,8 +21,8 @@ import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -94,7 +94,7 @@ public class KeyboardInterceptorTest {
when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L);
mInterceptor.onKeyEvent(event, 0);
- verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
+ verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt());
assertFalse(mHandler.hasMessages());
}
@@ -106,7 +106,7 @@ public class KeyboardInterceptorTest {
mInterceptor.onKeyEvent(event, 0);
assertTrue(mHandler.hasMessages());
- verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
+ verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt());
when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
argThat(matchesKeyEvent(event)), eq(0))).thenReturn(0L);
@@ -123,13 +123,13 @@ public class KeyboardInterceptorTest {
mInterceptor.onKeyEvent(event, 0);
assertTrue(mHandler.hasMessages());
- verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
+ verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt());
when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L);
mHandler.sendAllMessages();
- verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
+ verify(mMockAms, times(0)).notifyKeyEvent(any(), anyInt());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index c75cfe67ab79..367f2d143acc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -29,15 +29,14 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.accessibilityservice.GestureDescription.GestureStep;
@@ -223,7 +222,7 @@ public class MotionEventInjectorTest {
verifyNoMoreInteractions(next);
reset(next);
- verifyZeroInteractions(mServiceInterface);
+ verifyNoMoreInteractions(mServiceInterface);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index ea83825cd810..a0482382fb41 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -18,12 +18,14 @@ package com.android.server.accessibility.autoclick;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK;
import static com.android.server.testutils.MockitoUtilsKt.eq;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,6 +45,7 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.BaseEventStreamTransformation;
import org.junit.After;
import org.junit.Before;
@@ -70,6 +73,19 @@ public class AutoclickControllerTest {
@Mock private WindowManager mMockWindowManager;
private AutoclickController mController;
+ private static class MotionEventCaptor extends BaseEventStreamTransformation {
+ public MotionEvent downEvent;
+
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ downEvent = event;
+ break;
+ }
+ }
+ }
+
@Before
public void setUp() {
mTestableLooper = TestableLooper.get(this);
@@ -533,6 +549,29 @@ public class AutoclickControllerTest {
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void triggerRightClickWithRevertToLeftClickEnabled_resetClickType() {
+ // Move mouse to initialize autoclick panel.
+ injectFakeMouseActionHoverMoveEvent();
+
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+ mController.clickPanelController.handleAutoclickTypeChange(AUTOCLICK_TYPE_RIGHT_CLICK);
+
+ // Set ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK to true.
+ Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
+ AccessibilityUtils.State.ON,
+ mTestableContext.getUserId());
+ mController.onChangeForTesting(/* selfChange= */ true,
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK));
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(false);
+ mController.mClickScheduler.run();
+ assertThat(mController.mClickScheduler.getRevertToLeftClickForTesting()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void pauseButton_flagOn_clickNotTriggeredWhenPaused() {
injectFakeMouseActionHoverMoveEvent();
@@ -660,6 +699,206 @@ public class AutoclickControllerTest {
assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
}
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickScrollPanel() {
+ assertThat(mController.mAutoclickScrollPanel).isNull();
+
+ injectFakeMouseActionHoverMoveEvent();
+
+ assertThat(mController.mAutoclickScrollPanel).isNotNull();
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickScrollPanel() {
+ assertThat(mController.mAutoclickScrollPanel).isNull();
+
+ injectFakeMouseActionHoverMoveEvent();
+
+ assertThat(mController.mAutoclickScrollPanel).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_hideAutoclickScrollPanel() {
+ injectFakeMouseActionHoverMoveEvent();
+ AutoclickScrollPanel mockAutoclickScrollPanel = mock(AutoclickScrollPanel.class);
+ mController.mAutoclickScrollPanel = mockAutoclickScrollPanel;
+
+ mController.onDestroy();
+
+ verify(mockAutoclickScrollPanel).hide();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void changeFromScrollToOtherClickType_hidesScrollPanel() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Set active click type to SCROLL.
+ mController.clickPanelController.handleAutoclickTypeChange(
+ AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL);
+
+ // Show the scroll panel.
+ mController.mAutoclickScrollPanel.show();
+ assertThat(mController.mAutoclickScrollPanel.isVisible()).isTrue();
+
+ // Change click type to LEFT_CLICK.
+ mController.clickPanelController.handleAutoclickTypeChange(
+ AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK);
+
+ // Verify scroll panel is hidden.
+ assertThat(mController.mAutoclickScrollPanel.isVisible()).isFalse();
+ }
+
+ @Test
+ public void sendClick_clickType_leftClick() {
+ MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+ mController.setNext(motionEventCaptor);
+
+ injectFakeMouseActionHoverMoveEvent();
+ // Set delay to zero so click is scheduled to run immediately.
+ mController.mClickScheduler.updateDelay(0);
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify left click sent.
+ assertThat(motionEventCaptor.downEvent).isNotNull();
+ assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
+ MotionEvent.BUTTON_PRIMARY);
+ }
+
+ @Test
+ public void sendClick_clickType_rightClick() {
+ MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+ mController.setNext(motionEventCaptor);
+
+ injectFakeMouseActionHoverMoveEvent();
+ // Set delay to zero so click is scheduled to run immediately.
+ mController.mClickScheduler.updateDelay(0);
+
+ // Set click type to right click.
+ mController.clickPanelController.handleAutoclickTypeChange(
+ AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK);
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify right click sent.
+ assertThat(motionEventCaptor.downEvent).isNotNull();
+ assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
+ MotionEvent.BUTTON_SECONDARY);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void sendClick_clickType_scroll_showsScrollPanelOnlyOnce() {
+ MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+ mController.setNext(motionEventCaptor);
+
+ injectFakeMouseActionHoverMoveEvent();
+ // Set delay to zero so click is scheduled to run immediately.
+ mController.mClickScheduler.updateDelay(0);
+
+ // Set click type to scroll.
+ mController.clickPanelController.handleAutoclickTypeChange(
+ AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL);
+
+ // Mock the scroll panel to verify interactions.
+ AutoclickScrollPanel mockScrollPanel = mock(AutoclickScrollPanel.class);
+ mController.mAutoclickScrollPanel = mockScrollPanel;
+
+ // First hover move event.
+ MotionEvent hoverMove1 = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove1.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove1, hoverMove1, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify scroll panel is shown once.
+ verify(mockScrollPanel, times(1)).show();
+ assertThat(motionEventCaptor.downEvent).isNull();
+
+ // Second significant hover move event to trigger another autoclick.
+ MotionEvent hoverMove2 = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 200,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 100f,
+ /* y= */ 100f,
+ /* metaState= */ 0);
+ hoverMove2.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove2, hoverMove2, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify scroll panel is still only shown once (not called again).
+ verify(mockScrollPanel, times(1)).show();
+ assertThat(motionEventCaptor.downEvent).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
+ MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+ mController.setNext(motionEventCaptor);
+
+ injectFakeMouseActionHoverMoveEvent();
+ // Set delay to zero so click is scheduled to run immediately.
+ mController.mClickScheduler.updateDelay(0);
+
+ // Set click type to right click.
+ mController.clickPanelController.handleAutoclickTypeChange(
+ AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK);
+ // Set mouse to hover panel.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+ mTestableLooper.processAllMessages();
+
+ // Verify left click is sent due to the mouse hovering the panel.
+ assertThat(motionEventCaptor.downEvent).isNotNull();
+ assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
+ MotionEvent.BUTTON_PRIMARY);
+ }
+
private void injectFakeMouseActionHoverMoveEvent() {
MotionEvent event = getFakeMotionHoverMoveEvent();
event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java
new file mode 100644
index 000000000000..02361ff259c2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.autoclick;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageButton;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test cases for {@link AutoclickScrollPanel}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class AutoclickScrollPanelTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Rule
+ public TestableContext mTestableContext =
+ new TestableContext(getInstrumentation().getContext());
+
+ @Mock private WindowManager mMockWindowManager;
+ @Mock private AutoclickScrollPanel.ScrollPanelControllerInterface mMockScrollPanelController;
+
+ private AutoclickScrollPanel mScrollPanel;
+
+ // Scroll panel buttons.
+ private ImageButton mUpButton;
+ private ImageButton mDownButton;
+ private ImageButton mLeftButton;
+ private ImageButton mRightButton;
+ private ImageButton mExitButton;
+
+ @Before
+ public void setUp() {
+ mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
+ mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager,
+ mMockScrollPanelController);
+
+ View contentView = mScrollPanel.getContentViewForTesting();
+
+ // Initialize buttons.
+ mUpButton = contentView.findViewById(R.id.scroll_up);
+ mDownButton = contentView.findViewById(R.id.scroll_down);
+ mLeftButton = contentView.findViewById(R.id.scroll_left);
+ mRightButton = contentView.findViewById(R.id.scroll_right);
+ mExitButton = contentView.findViewById(R.id.scroll_exit);
+ }
+
+ @Test
+ public void show_addsViewToWindowManager() {
+ mScrollPanel.show();
+
+ // Verify view is added to window manager.
+ verify(mMockWindowManager).addView(any(), any(WindowManager.LayoutParams.class));
+
+ // Verify isVisible reflects correct state.
+ assertThat(mScrollPanel.isVisible()).isTrue();
+ }
+
+ @Test
+ public void show_alreadyVisible_doesNotAddAgain() {
+ // Show twice.
+ mScrollPanel.show();
+ mScrollPanel.show();
+
+ // Verify addView was only called once.
+ verify(mMockWindowManager, times(1)).addView(any(), any());
+ }
+
+ @Test
+ public void hide_removesViewFromWindowManager() {
+ // First show the panel.
+ mScrollPanel.show();
+ // Then hide it.
+ mScrollPanel.hide();
+ // Verify view is removed from window manager.
+ verify(mMockWindowManager).removeView(any());
+ // Verify scroll panel is hidden.
+ assertThat(mScrollPanel.isVisible()).isFalse();
+ }
+
+ @Test
+ public void initialState_correctButtonVisibility() {
+ // Verify all expected buttons exist in the view.
+ assertThat(mUpButton.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mDownButton.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mLeftButton.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mRightButton.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mExitButton.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void directionButtons_onHover_callsHandleScroll() {
+ // Test up button.
+ triggerHoverEvent(mUpButton);
+ verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_UP);
+
+ // Test down button.
+ triggerHoverEvent(mDownButton);
+ verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_DOWN);
+
+ // Test left button.
+ triggerHoverEvent(mLeftButton);
+ verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_LEFT);
+
+ // Test right button.
+ triggerHoverEvent(mRightButton);
+ verify(mMockScrollPanelController).handleScroll(AutoclickScrollPanel.DIRECTION_RIGHT);
+ }
+
+ @Test
+ public void exitButton_onHover_callsExitScrollMode() {
+ // Test exit button.
+ triggerHoverEvent(mExitButton);
+ verify(mMockScrollPanelController).exitScrollMode();
+ }
+
+ // Helper method to simulate a hover event on a view.
+ private void triggerHoverEvent(View view) {
+ MotionEvent event = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ MotionEvent.ACTION_HOVER_ENTER,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+
+ // Dispatch the event to the view's OnHoverListener.
+ view.dispatchGenericMotionEvent(event);
+ event.recycle();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index f7b16c808c50..8bbbca0b7ed4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -198,14 +198,80 @@ public class AutoclickTypePanelTest {
}
@Test
+ public void clickLeftClickButton_resumeAutoClick() {
+ // Pause autoclick.
+ mPauseButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ // Click the button and verify autoclick resumes.
+ mLeftClickButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
+ @Test
+ public void clickRightClickButton_resumeAutoClick() {
+ // Pause autoclick.
+ mPauseButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ // Click the button and verify autoclick resumes.
+ mRightClickButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
+ @Test
+ public void clickDoubleClickButton_resumeAutoClick() {
+ // Pause autoclick.
+ mPauseButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ // Click the button and verify autoclick resumes.
+ mDoubleClickButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
+ @Test
+ public void clickDragButton_resumeAutoClick() {
+ // Pause autoclick.
+ mPauseButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ // Click the button and verify autoclick resumes.
+ mDragButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
+ @Test
+ public void clickScrollButton_resumeAutoClick() {
+ // Pause autoclick.
+ mPauseButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ // Click the button and verify autoclick resumes.
+ mScrollButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
+ @Test
+ public void clickPositionButton_resumeAutoClick() {
+ // Pause autoclick.
+ mPauseButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+ // Click the button and verify autoclick resumes.
+ mPositionButton.callOnClick();
+ assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+ }
+
+ @Test
public void moveToNextCorner_positionButton_rotatesThroughAllPositions() {
// Define all positions in sequence
int[][] expectedPositions = {
- {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
- {1, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
- {2, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
- {3, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
- {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}
+ {CORNER_BOTTOM_RIGHT, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
+ {CORNER_BOTTOM_LEFT, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
+ {CORNER_TOP_LEFT, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
+ {CORNER_TOP_RIGHT, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
+ {CORNER_BOTTOM_RIGHT, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}
};
// Check initial position
@@ -270,7 +336,7 @@ public class AutoclickTypePanelTest {
int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
// Verify initial corner is bottom-right.
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_BOTTOM_RIGHT);
dispatchDragSequence(contentView,
@@ -279,7 +345,7 @@ public class AutoclickTypePanelTest {
// Verify snapping to the right.
assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.TOP);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_TOP_RIGHT);
}
@@ -293,7 +359,7 @@ public class AutoclickTypePanelTest {
int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
// Verify initial corner is bottom-right.
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_BOTTOM_RIGHT);
dispatchDragSequence(contentView,
@@ -302,7 +368,7 @@ public class AutoclickTypePanelTest {
// Verify snapping to the left.
assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_BOTTOM_LEFT);
}
@@ -319,7 +385,7 @@ public class AutoclickTypePanelTest {
// Verify panel is positioned at default bottom-right corner.
WindowManager.LayoutParams params = panel.getLayoutParamsForTesting();
- assertThat(panel.getCurrentCornerIndexForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT);
+ assertThat(panel.getCurrentCornerForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT);
assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.BOTTOM);
assertThat(params.x).isEqualTo(15); // Default edge margin.
assertThat(params.y).isEqualTo(90); // Default bottom offset.
@@ -353,7 +419,7 @@ public class AutoclickTypePanelTest {
assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
assertThat(params.x).isEqualTo(15);
assertThat(params.y).isEqualTo(30);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo(
CORNER_TOP_LEFT);
}
@@ -392,7 +458,7 @@ public class AutoclickTypePanelTest {
assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
assertThat(params.x).isEqualTo(15); // PANEL_EDGE_MARGIN
assertThat(params.y).isEqualTo(panelLocation[1] + 10);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo(
CORNER_BOTTOM_LEFT);
}
@@ -453,7 +519,7 @@ public class AutoclickTypePanelTest {
private void verifyPanelPosition(int[] expectedPosition) {
WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo(
expectedPosition[0]);
assertThat(params.gravity).isEqualTo(expectedPosition[1]);
assertThat(params.x).isEqualTo(expectedPosition[2]);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1af59daa9c78..5922b12edc1e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -37,6 +37,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,6 +47,7 @@ import android.content.Context;
import android.graphics.PointF;
import android.os.Looper;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
@@ -504,6 +506,36 @@ public class TouchExplorerTest {
assertThat(sentRawEvent.getDisplayId()).isEqualTo(rawDisplayId);
}
+ @Test
+ @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
+ public void handleMotionEventStateTouchExploring_pointerUp_doesNotSendToManager() {
+ mTouchExplorer.getState().setServiceDetectsGestures(true);
+ mTouchExplorer.getState().clear();
+
+ mLastEvent = pointerDownEvent();
+ mTouchExplorer.getState().startTouchExploring();
+ MotionEvent event = fromTouchscreen(pointerUpEvent());
+
+ mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0);
+
+ verify(mMockAms, never()).sendMotionEventToListeningServices(event);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
+ public void handleMotionEventStateTouchExploring_pointerUp_sendsToManager() {
+ mTouchExplorer.getState().setServiceDetectsGestures(true);
+ mTouchExplorer.getState().clear();
+
+ mLastEvent = pointerDownEvent();
+ mTouchExplorer.getState().startTouchExploring();
+ MotionEvent event = fromTouchscreen(pointerUpEvent());
+
+ mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0);
+
+ verify(mMockAms).sendMotionEventToListeningServices(event);
+ }
+
/**
* Used to play back event data of a gesture by parsing the log into MotionEvents and sending
* them to TouchExplorer.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java
new file mode 100644
index 000000000000..3e7d9fd05327
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.gestures;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
+
+import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR;
+import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.Display;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class TouchStateTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private TouchState mTouchState;
+ @Mock private AccessibilityManagerService mMockAms;
+
+ @Before
+ public void setup() {
+ mTouchState = new TouchState(Display.DEFAULT_DISPLAY, mMockAms);
+ }
+
+ @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
+ @Test
+ public void injectedEvent_interactionEnd_pointerDown_startsTouchExploring() {
+ mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 1;
+ mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
+ assertThat(mTouchState.getState()).isEqualTo(STATE_TOUCH_EXPLORING);
+ }
+
+ @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
+ @Test
+ public void injectedEvent_interactionEnd_pointerUp_clears() {
+ mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 0;
+ mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
+ assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR);
+ }
+
+ @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
+ @Test
+ public void injectedEvent_interactionEnd_clears() {
+ mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
+ assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index ac27a971102a..9ec99c651691 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -30,7 +30,10 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -248,6 +251,40 @@ public class FullScreenMagnificationControllerTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testRegister_RegistersPointerMotionFilter() {
+ register(DISPLAY_0);
+
+ verify(mMockInputManager).registerAccessibilityPointerMotionFilter(
+ any(InputManagerInternal.AccessibilityPointerMotionFilter.class));
+
+ // If a filter is already registered, adding a display won't invoke another filter
+ // registration.
+ clearInvocations(mMockInputManager);
+ register(DISPLAY_1);
+ register(INVALID_DISPLAY);
+
+ verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter(
+ any(InputManagerInternal.AccessibilityPointerMotionFilter.class));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testUnregister_UnregistersPointerMotionFilter() {
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ clearInvocations(mMockInputManager);
+
+ mFullScreenMagnificationController.unregister(DISPLAY_1);
+ // There's still an active display. Don't unregister yet.
+ verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter(
+ nullable(InputManagerInternal.AccessibilityPointerMotionFilter.class));
+
+ mFullScreenMagnificationController.unregister(DISPLAY_0);
+ verify(mMockInputManager, times(1)).registerAccessibilityPointerMotionFilter(isNull());
+ }
+
+ @Test
public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i);
@@ -699,6 +736,63 @@ public class FullScreenMagnificationControllerTest {
}
@Test
+ public void testSetOffset_whileMagnifying_offsetsMove() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setOffset_whileMagnifying_offsetsMove(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setOffset_whileMagnifying_offsetsMove(int displayId) {
+ register(displayId);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ for (final float scale : new float[]{2.0f, 2.5f, 3.0f}) {
+ assertTrue(mFullScreenMagnificationController
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
+ SERVICE_ID_1));
+ mMessageCapturingHandler.sendAllMessages();
+
+ for (final PointF center : new PointF[]{
+ INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER,
+ INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER}) {
+ Mockito.clearInvocations(mMockWindowManager);
+ PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
+ mFullScreenMagnificationController.setOffset(displayId, newOffsets.x, newOffsets.y,
+ SERVICE_ID_1);
+ mMessageCapturingHandler.sendAllMessages();
+
+ MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
+ verify(mMockWindowManager)
+ .setMagnificationSpec(eq(displayId), argThat(closeTo(expectedSpec)));
+ assertEquals(center.x, mFullScreenMagnificationController.getCenterX(displayId),
+ 0.0);
+ assertEquals(center.y, mFullScreenMagnificationController.getCenterY(displayId),
+ 0.0);
+ verify(mMockValueAnimator, times(0)).start();
+ }
+ }
+ }
+
+ @Test
+ public void testSetOffset_whileNotMagnifying_hasNoEffect() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setOffset_whileNotMagnifying_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setOffset_whileNotMagnifying_hasNoEffect(int displayId) {
+ register(displayId);
+ Mockito.reset(mMockWindowManager);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ mFullScreenMagnificationController.setOffset(displayId, 100, 100, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ mFullScreenMagnificationController.setOffset(displayId, 200, 200, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
public void testStartFling_whileMagnifying_flings() throws InterruptedException {
for (int i = 0; i < DISPLAY_COUNT; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 5c126d1f5d3f..31cdd6c3a658 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -43,8 +43,8 @@ import static org.junit.Assume.assumeTrue;
import static org.mockito.AdditionalMatchers.gt;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
@@ -1419,6 +1419,12 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testMouseMoveEventsDoNotMoveMagnifierViewport() {
+ runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
public void testStylusMoveEventsDoNotMoveMagnifierViewport() {
runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
}
@@ -1467,11 +1473,28 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
+ // Note that this means mouse hover shouldn't be handled here.
+ // FullScreenMagnificationPointerMotionEventFilter handles mouse input events.
+ runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() {
+ // TODO(b/398984690): We will revisit the behavior.
+ runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
public void testMouseHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
public void testStylusHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS);
}
@@ -1497,6 +1520,7 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
public void testMouseMoveEventsMoveMagnifierViewport() {
final EventCaptor eventCaptor = new EventCaptor();
mMgh.setNext(eventCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java
new file mode 100644
index 000000000000..a8315d4eb3a5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.accessibility.magnification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class FullScreenMagnificationPointerMotionEventFilterTest {
+ @Mock
+ private FullScreenMagnificationController mMockFullScreenMagnificationController;
+
+ private FullScreenMagnificationPointerMotionEventFilter mFilter;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mFilter = new FullScreenMagnificationPointerMotionEventFilter(
+ mMockFullScreenMagnificationController);
+ }
+
+ @Test
+ public void inactiveDisplay_doNothing() {
+ when(mMockFullScreenMagnificationController.isActivated(anyInt())).thenReturn(false);
+
+ float[] delta = new float[]{1.f, 2.f};
+ float[] result = mFilter.filterPointerMotionEvent(delta[0], delta[1], 3.0f, 4.0f, 0);
+ assertThat(result).isEqualTo(delta);
+ }
+
+ @Test
+ public void testContinuousMove() {
+ when(mMockFullScreenMagnificationController.isActivated(anyInt())).thenReturn(true);
+ when(mMockFullScreenMagnificationController.getScale(anyInt())).thenReturn(3.f);
+
+ float[] delta = new float[]{5.f, 10.f};
+ float[] result = mFilter.filterPointerMotionEvent(delta[0], delta[1], 20.f, 30.f, 0);
+ assertThat(result).isEqualTo(delta);
+ // At the first cursor move, it goes to (20, 30) + (5, 10) = (25, 40). The scale is 3.0.
+ // The expected offset is (-25 * (3-1), -40 * (3-1)) = (-50, -80).
+ verify(mMockFullScreenMagnificationController)
+ .setOffset(eq(0), eq(-50.f), eq(-80.f), anyInt());
+
+ float[] delta2 = new float[]{10.f, 5.f};
+ float[] result2 = mFilter.filterPointerMotionEvent(delta2[0], delta2[1], 25.f, 40.f, 0);
+ assertThat(result2).isEqualTo(delta2);
+ // At the second cursor move, it goes to (25, 40) + (10, 5) = (35, 40). The scale is 3.0.
+ // The expected offset is (-35 * (3-1), -45 * (3-1)) = (-70, -90).
+ verify(mMockFullScreenMagnificationController)
+ .setOffset(eq(0), eq(-70.f), eq(-90.f), anyInt());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 0c92abce7254..9a241043c52f 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -19,11 +19,11 @@ package com.android.server.accounts;
import static android.database.sqlite.SQLiteDatabase.deleteDatabase;
import static org.mockito.ArgumentMatchers.contains;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 61ac74cc3490..514c07827ae9 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 1627f683cd3e..6d656609c759 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -58,10 +58,10 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
index 01fee7f66497..918159f9262b 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
@@ -97,7 +97,8 @@ public class DiscreteAppOpSqlPersistenceTest {
mDiscreteRegistry.recordDiscreteAccess(opEvent2);
List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
- assertThat(discreteOps.size()).isEqualTo(1);
+ assertWithMessage("Expected list size is 1, but the list is: " + discreteOps)
+ .that(discreteOps.size()).isEqualTo(1);
assertThat(discreteOps).contains(opEvent);
}
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
index ae973be17904..56f802b278c6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
@@ -89,8 +89,7 @@ public class DiscreteAppOpXmlPersistenceTest {
int attributionChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
- uidState, accessTime, duration, attributionFlags, attributionChainId,
- DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP);
+ uidState, accessTime, duration, attributionFlags, attributionChainId);
// Verify in-memory object is correct
fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
@@ -121,8 +120,7 @@ public class DiscreteAppOpXmlPersistenceTest {
int attributionChainId = 10;
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
- uidState, accessTime, duration, attributionFlags, attributionChainId,
- DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP);
+ uidState, accessTime, duration, attributionFlags, attributionChainId);
fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
duration, uidState, opFlags, attributionFlags, attributionChainId);
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
index 8eea1c73d4f2..6c66f149baa7 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
@@ -70,7 +70,7 @@ public class DiscreteOpsMigrationAndRollbackTest {
opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(),
opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(),
opEvent.getDuration(), opEvent.getAttributionFlags(),
- (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
+ (int) opEvent.getChainId());
}
xmlRegistry.writeAndClearOldAccessHistory();
assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT);
@@ -104,7 +104,7 @@ public class DiscreteOpsMigrationAndRollbackTest {
opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(),
opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(),
opEvent.getDuration(), opEvent.getAttributionFlags(),
- (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
+ (int) opEvent.getChainId());
}
// flush records from cache to the database.
sqlRegistry.shutdown();
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 3475c8f5444d..20a95e90b668 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -34,7 +34,6 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal;
@@ -196,7 +195,7 @@ public class AttentionManagerServiceTest {
@Test
public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() {
mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal);
- verifyZeroInteractions(mMockProximityUpdateCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
index d5638e9346f6..d89cf7bb5db5 100644
--- a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
@@ -16,7 +16,7 @@
package com.android.server.audio;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -107,13 +107,13 @@ public class MusicFxHelperTest {
List<ResolveInfo> list, int bind, int broadcast, String packageName, int audioSession,
int uid) {
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
- doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt());
+ doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(any(), anyInt());
if (list != null && list.size() != 0) {
try {
doReturn(uid).when(mMockPackageManager)
- .getPackageUidAsUser(eq(packageName), anyObject(), anyInt());
+ .getPackageUidAsUser(eq(packageName), any(), anyInt());
doReturn(mMusicFxUid).when(mMockPackageManager)
- .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt());
+ .getPackageUidAsUser(eq(mMusicFxPkgName), any(), anyInt());
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "NameNotFoundException: " + e);
}
@@ -123,8 +123,8 @@ public class MusicFxHelperTest {
packageName, audioSession);
mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
verify(mMockContext, times(bind))
- .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
- verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject());
+ .bindServiceAsUser(any(), any(), anyInt(), any());
+ verify(mMockContext, times(broadcast)).sendBroadcastAsUser(any(), any());
}
/**
@@ -136,13 +136,13 @@ public class MusicFxHelperTest {
List<ResolveInfo> list, int unBind, int broadcast, String packageName,
int audioSession, int uid) {
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
- doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt());
+ doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(any(), anyInt());
if (list != null && list.size() != 0) {
try {
doReturn(uid).when(mMockPackageManager)
- .getPackageUidAsUser(eq(packageName), anyObject(), anyInt());
+ .getPackageUidAsUser(eq(packageName), any(), anyInt());
doReturn(mMusicFxUid).when(mMockPackageManager)
- .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt());
+ .getPackageUidAsUser(eq(mMusicFxPkgName), any(), anyInt());
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "NameNotFoundException: " + e);
}
@@ -151,8 +151,8 @@ public class MusicFxHelperTest {
Intent intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION,
packageName, audioSession);
mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
- verify(mMockContext, times(unBind)).unbindService(anyObject());
- verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject());
+ verify(mMockContext, times(unBind)).unbindService(any());
+ verify(mMockContext, times(broadcast)).sendBroadcastAsUser(any(), any());
}
/**
@@ -160,8 +160,8 @@ public class MusicFxHelperTest {
*/
private void sendMessage(int msgId, int uid, int unBinds, int broadcasts) {
mMusicFxHelper.handleMessage(Message.obtain(null, msgId, uid /* arg1 */, 0 /* arg2 */));
- verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(anyObject(), anyObject());
- verify(mMockContext, times(unBinds)).unbindService(anyObject());
+ verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(any(), any());
+ verify(mMockContext, times(unBinds)).unbindService(any());
}
/**
@@ -209,15 +209,15 @@ public class MusicFxHelperTest {
intent.setPackage(mTestPkg1);
mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
verify(mMockContext, times(0))
- .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
- verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject());
+ .bindServiceAsUser(any(), any(), anyInt(), any());
+ verify(mMockContext, times(0)).sendBroadcastAsUser(any(), any());
intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, null, 1);
intent.setPackage(mTestPkg2);
mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
verify(mMockContext, times(0))
- .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
- verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject());
+ .bindServiceAsUser(any(), any(), anyInt(), any());
+ verify(mMockContext, times(0)).sendBroadcastAsUser(any(), any());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index d8a616214f96..a5d6a19d1491 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -42,7 +42,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -451,7 +450,7 @@ public class AuthSessionTest {
assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
: BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
- verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+ verify(mBiometricContext).updateContext((OperationContextExt) any(),
eq(session.isCrypto()));
// start fingerprint sensor if it was delayed
@@ -554,7 +553,7 @@ public class AuthSessionTest {
session.onDialogDismissed(DISMISSED_REASON_BIOMETRIC_CONFIRMED, null);
verify(mBiometricFrameworkStatsLogger, times(1)).authenticate(
- (OperationContextExt) anyObject(),
+ (OperationContextExt) any(),
eq(BiometricsProtoEnums.MODALITY_FACE),
eq(BiometricsProtoEnums.ACTION_UNKNOWN),
eq(BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT),
@@ -582,10 +581,10 @@ public class AuthSessionTest {
session.onDialogDismissed(DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, null);
verify(mBiometricFrameworkStatsLogger, never()).authenticate(
- anyObject(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(),
+ any(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(),
anyBoolean(), anyInt(), eq(-1f));
verify(mBiometricFrameworkStatsLogger, never()).error(
- anyObject(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(),
+ any(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyLong(), anyInt(),
anyInt(), anyInt());
}
@@ -605,7 +604,7 @@ public class AuthSessionTest {
session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null);
verify(mBiometricFrameworkStatsLogger, times(1)).error(
- (OperationContextExt) anyObject(),
+ (OperationContextExt) any(),
eq(BiometricsProtoEnums.MODALITY_FACE),
eq(BiometricsProtoEnums.ACTION_AUTHENTICATE),
eq(BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT),
@@ -632,7 +631,7 @@ public class AuthSessionTest {
session.onDialogDismissed(DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS, null);
verify(mBiometricFrameworkStatsLogger, times(1)).error(
- (OperationContextExt) anyObject(),
+ (OperationContextExt) any(),
eq(BiometricsProtoEnums.MODALITY_FACE),
eq(BiometricsProtoEnums.ACTION_AUTHENTICATE),
eq(BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT),
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index 7a770338a34b..f4e87177e072 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -172,7 +172,7 @@ public class FaceDetectClientTest {
client.onInteractionDetected();
client.stopHalOperation();
- verifyZeroInteractions(mVibrator);
+ verifyNoMoreInteractions(mVibrator);
}
private FaceDetectClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index b445226be60f..4fa75b9823e0 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -23,7 +23,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -132,8 +132,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -147,7 +147,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager, never()).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -157,8 +157,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -172,7 +172,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager, never()).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -187,7 +187,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager, never()).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -203,7 +203,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -216,8 +216,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -230,8 +230,8 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionAllowlistManager);
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionAllowlistManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -248,7 +248,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -265,7 +265,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionAllowlistManager).start(anyLong());
verify(mMockContentProtectionAllowlistManager).stop();
- verifyZeroInteractions(mMockContentProtectionConsentManager);
+ verifyNoMoreInteractions(mMockContentProtectionConsentManager);
}
@Test
@@ -513,7 +513,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
@@ -528,7 +528,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
index 195ab68427b9..9d37b99c5bf4 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.Handler;
@@ -98,10 +98,10 @@ public class ContentProtectionAllowlistManagerTest {
@Test
public void constructor() {
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -110,10 +110,10 @@ public class ContentProtectionAllowlistManagerTest {
mTestLooper.dispatchAll();
assertThat(mHandler.hasMessagesOrCallbacks()).isTrue();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -126,8 +126,8 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor, never()).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -142,8 +142,8 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor, never()).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -153,11 +153,11 @@ public class ContentProtectionAllowlistManagerTest {
mContentProtectionAllowlistManager.stop();
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
verify(mMockPackageMonitor, never()).register(any(), any(), any());
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -169,11 +169,11 @@ public class ContentProtectionAllowlistManagerTest {
mContentProtectionAllowlistManager.stop();
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
verify(mMockPackageMonitor, never()).register(any(), any(), any());
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -188,8 +188,8 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -205,8 +205,8 @@ public class ContentProtectionAllowlistManagerTest {
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -223,8 +223,8 @@ public class ContentProtectionAllowlistManagerTest {
assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
verify(mMockPackageMonitor, times(2)).register(any(), eq(UserHandle.ALL), eq(mHandler));
verify(mMockPackageMonitor).unregister();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -232,10 +232,10 @@ public class ContentProtectionAllowlistManagerTest {
boolean actual = mContentProtectionAllowlistManager.isAllowed(FIRST_PACKAGE_NAME);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -248,9 +248,9 @@ public class ContentProtectionAllowlistManagerTest {
boolean actual = manager.isAllowed(SECOND_PACKAGE_NAME);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
@@ -263,9 +263,9 @@ public class ContentProtectionAllowlistManagerTest {
boolean actual = manager.isAllowed(FIRST_PACKAGE_NAME);
assertThat(actual).isTrue();
- verifyZeroInteractions(mMockContentCaptureManagerService);
- verifyZeroInteractions(mMockPackageMonitor);
- verifyZeroInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockContentCaptureManagerService);
+ verifyNoMoreInteractions(mMockPackageMonitor);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
}
@Test
@@ -276,8 +276,8 @@ public class ContentProtectionAllowlistManagerTest {
manager.mPackageMonitor.onSomePackagesChanged();
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -291,7 +291,7 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockRemoteContentProtectionService)
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -309,7 +309,7 @@ public class ContentProtectionAllowlistManagerTest {
// Does not rethrow
verify(mMockRemoteContentProtectionService)
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -321,8 +321,8 @@ public class ContentProtectionAllowlistManagerTest {
manager.mPackageMonitor.onSomePackagesChanged();
verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService();
- verifyZeroInteractions(mMockRemoteContentProtectionService);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockRemoteContentProtectionService);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -338,7 +338,7 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
verify(mMockRemoteContentProtectionService)
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
@@ -355,7 +355,7 @@ public class ContentProtectionAllowlistManagerTest {
verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService();
verify(mMockRemoteContentProtectionService, times(2))
.onUpdateAllowlistRequest(mMockAllowlistCallback);
- verifyZeroInteractions(mMockAllowlistCallback);
+ verifyNoMoreInteractions(mMockAllowlistCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
index b012aaaed3bf..cd36a1889d2f 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
@@ -27,7 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyCache;
@@ -112,8 +112,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -125,8 +125,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -138,8 +138,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -152,7 +152,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -166,7 +166,7 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -179,7 +179,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isFalse();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -192,7 +192,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -289,8 +289,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -302,8 +302,8 @@ public class ContentProtectionConsentManagerTest {
boolean actual = manager.isConsentGranted(TEST_USER_ID);
assertThat(actual).isFalse();
- verifyZeroInteractions(mMockDevicePolicyManagerInternal);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyManagerInternal);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -316,7 +316,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(actual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -339,7 +339,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -362,7 +362,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -385,7 +385,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
@Test
@@ -408,7 +408,7 @@ public class ContentProtectionConsentManagerTest {
assertThat(thirdActual).isTrue();
verify(mMockDevicePolicyManagerInternal, times(3)).isUserOrganizationManaged(TEST_USER_ID);
- verifyZeroInteractions(mMockDevicePolicyCache);
+ verifyNoMoreInteractions(mMockDevicePolicyCache);
}
private void putGlobalSettings(String key, int value) {
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
index 6a7e2865fb32..563a6799e9e5 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -20,7 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -87,7 +87,7 @@ public class RemoteContentProtectionServiceTest {
@Test
public void doesNotAutoConnect() {
assertThat(mConnectCallCount).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionService);
+ verifyNoMoreInteractions(mMockContentProtectionService);
}
@Test
@@ -124,7 +124,7 @@ public class RemoteContentProtectionServiceTest {
mRemoteContentProtectionService.onServiceConnectionStatusChanged(
mMockContentProtectionService, /* isConnected= */ true);
- verifyZeroInteractions(mMockContentProtectionService);
+ verifyNoMoreInteractions(mMockContentProtectionService);
assertThat(mConnectCallCount).isEqualTo(0);
}
@@ -133,7 +133,7 @@ public class RemoteContentProtectionServiceTest {
mRemoteContentProtectionService.onServiceConnectionStatusChanged(
mMockContentProtectionService, /* isConnected= */ false);
- verifyZeroInteractions(mMockContentProtectionService);
+ verifyNoMoreInteractions(mMockContentProtectionService);
assertThat(mConnectCallCount).isEqualTo(0);
}
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
index 0f3f27aa2896..9e98af32d6e5 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -207,7 +207,7 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
"unsupportedKey", providerPendingIntentResponse);
- verifyZeroInteractions(mGetRequestSession);
+ verifyNoMoreInteractions(mGetRequestSession);
}
@Test
@@ -216,7 +216,7 @@ public class ProviderRegistryGetSessionTest {
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, null);
- verifyZeroInteractions(mGetRequestSession);
+ verifyNoMoreInteractions(mGetRequestSession);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 5582e13cbb4d..d5fe0bf9cfdf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -23,9 +23,9 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 30aa8cebdff6..c50c62323212 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -88,7 +88,6 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import static org.testng.Assert.assertThrows;
@@ -143,6 +142,7 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.KeyChain;
import android.security.keystore.AttestationUtils;
+import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.test.MoreAsserts;
@@ -5304,7 +5304,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// both the user restriction and the policy were set by the PO.
verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(
MANAGED_PROFILE_USER_ID);
- verifyZeroInteractions(getServices().recoverySystem);
+ verifyNoMoreInteractions(getServices().recoverySystem);
}
@Test
@@ -5338,7 +5338,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// not wiped.
verify(getServices().userManagerInternal, never())
.removeUserEvenWhenDisallowed(anyInt());
- verifyZeroInteractions(getServices().recoverySystem);
+ verifyNoMoreInteractions(getServices().recoverySystem);
}
@Test
@@ -5379,7 +5379,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
// DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped.
- verifyZeroInteractions(getServices().recoverySystem);
+ verifyNoMoreInteractions(getServices().recoverySystem);
verify(getServices().userManagerInternal, never())
.removeUserEvenWhenDisallowed(anyInt());
}
@@ -7534,7 +7534,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().notificationManager, never())
.notify(anyInt(), any(Notification.class));
// Apps shouldn't be suspended.
- verifyZeroInteractions(getServices().ipackageManager);
+ verifyNoMoreInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
setUserUnlocked(CALLER_USER_HANDLE, false);
@@ -7547,7 +7547,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().notificationManager, never())
.notify(anyInt(), any(Notification.class));
// Apps shouldn't be suspended.
- verifyZeroInteractions(getServices().ipackageManager);
+ verifyNoMoreInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
// Pretend the alarm went off.
@@ -7560,7 +7560,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(getServices().notificationManager, times(1))
.notifyAsUser(any(), anyInt(), any(), any());
// Apps shouldn't be suspended yet.
- verifyZeroInteractions(getServices().ipackageManager);
+ verifyNoMoreInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
clearInvocations(getServices().notificationManager);
@@ -7569,7 +7569,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
// Verify the alarm was not set.
- verifyZeroInteractions(getServices().alarmManager);
+ verifyNoMoreInteractions(getServices().alarmManager);
// Now the user should see a notification about suspended apps.
verify(getServices().notificationManager, times(1))
.notifyAsUser(any(), anyInt(), any(), any());
@@ -8715,6 +8715,47 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
}
+ @RequiresFlagsEnabled(Flags.FLAG_REMOVE_MANAGED_ESIM_ON_WORK_PROFILE_DELETION)
+ @Test
+ public void testManagedProfileDeleted_managedEmbeddedSubscriptionDeleted() throws Exception {
+ // Setup PO mode.
+ setupProfileOwner();
+ // Mock SubscriptionManager to return a subscription managed by the profile owner package.
+ int managedSubscriptionId = 42;
+ SubscriptionInfo managedSubscription = new SubscriptionInfo.Builder().setCardId(1).setId(
+ managedSubscriptionId).setGroupOwner(admin1.getPackageName()).build();
+ when(getServices().subscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
+ List.of(managedSubscription));
+
+ // Send a ACTION_MANAGED_PROFILE_REMOVED broadcast to emulate a managed profile being
+ // removed.
+ sendBroadcastWithUser(dpms, Intent.ACTION_MANAGED_PROFILE_REMOVED, CALLER_USER_HANDLE);
+
+ // Verify that EuiccManager was called to delete the subscription.
+ verify(getServices().euiccManager).deleteSubscription(eq(managedSubscriptionId), any());
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_REMOVE_MANAGED_ESIM_ON_WORK_PROFILE_DELETION)
+ @Test
+ public void testManagedProfileDeleted_flagDisabled_managedEmbeddedSubscriptionDeleted()
+ throws Exception {
+ // Set up PO mode.
+ setupProfileOwner();
+ // Mock SubscriptionManager to return a subscription managed by the profile owner package.
+ int managedSubscriptionId = 42;
+ SubscriptionInfo managedSubscription = new SubscriptionInfo.Builder().setCardId(1).setId(
+ managedSubscriptionId).setGroupOwner(admin1.getPackageName()).build();
+ when(getServices().subscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
+ List.of(managedSubscription));
+
+ // Send a ACTION_MANAGED_PROFILE_REMOVED broadcast to emulate a managed profile being
+ // removed.
+ sendBroadcastWithUser(dpms, Intent.ACTION_MANAGED_PROFILE_REMOVED, CALLER_USER_HANDLE);
+
+ // Verify that EuiccManager was not called to delete the subscription.
+ verifyNoMoreInteractions(getServices().euiccManager);
+ }
+
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 00b0c558b4e3..479af73bae3c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -253,6 +253,8 @@ public class DpmMockContext extends MockContext {
return mMockSystemServices.subscriptionManager;
case Context.USB_SERVICE:
return mMockSystemServices.usbManager;
+ case Context.EUICC_SERVICE:
+ return mMockSystemServices.euiccManager;
}
throw new UnsupportedOperationException();
}
@@ -487,6 +489,14 @@ public class DpmMockContext extends MockContext {
}
@Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) {
+ mMockSystemServices.registerReceiver(receiver, filter, scheduler);
+ return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler, flags);
+ }
+
+ @Override
public void unregisterReceiver(BroadcastReceiver receiver) {
mMockSystemServices.unregisterReceiver(receiver);
spiedContext.unregisterReceiver(receiver);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 03aaeb7e0db8..b52eb0a6857a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 3e4448c1dafa..d01fa91e22c2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -68,6 +68,7 @@ import android.provider.Settings;
import android.security.KeyChain;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccManager;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.util.ArrayMap;
@@ -151,6 +152,7 @@ public class MockSystemServices {
public final File dataDir;
public final PolicyPathProvider pathProvider;
public final SupervisionManagerInternal supervisionManagerInternal;
+ public final EuiccManager euiccManager;
private final Map<String, PackageState> mTestPackageStates = new ArrayMap<>();
@@ -206,6 +208,7 @@ public class MockSystemServices {
roleManagerForMock = mock(RoleManagerForMock.class);
subscriptionManager = mock(SubscriptionManager.class);
supervisionManagerInternal = mock(SupervisionManagerInternal.class);
+ euiccManager = mock(EuiccManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 0a696ef44897..b01895a527c6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -27,7 +27,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index b0ffebb973a1..aa1d5835bfc8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2295,6 +2295,38 @@ public class HdmiCecLocalDeviceTvTest {
.hasSize(1);
}
+ @Test
+ public void onOneTouchPlay_wakeUp_exist_device() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+
+ // Go to standby to trigger RequestActiveSourceAction for playback_1
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mNativeWrapper.clearResultMessages();
+
+ // turn off TV and wake up with one touch play
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ // FakePowerManagerWrapper#wakeUp() doesn't broadcast Intent.ACTION_SCREEN_ON
+ // manually trigger onWakeUp to mock OTP
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
+ }
+
@Test
public void handleReportAudioStatus_SamOnAvrStandby_startSystemAudioActionFromTv() {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 50cfa753ebdb..57e9cf4bf48b 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -95,8 +95,8 @@ public class LocaleManagerBackupRestoreTest {
private static final String TEST_LOCALES_XML_TAG = "locales";
private static final int DEFAULT_USER_ID = 0;
private static final int WORK_PROFILE_USER_ID = 10;
- private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
- private static final int WORK_PROFILE_UID = Binder.getCallingUid() + 1000100;
+ private static final int DEFAULT_UID = 100;
+ private static final int WORK_PROFILE_UID = 1000100;
private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
private static final LocaleList DEFAULT_LOCALES =
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index e20f1e7065d4..a39f07105eab 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -31,7 +31,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.ActivityManagerInternal;
import android.content.Context;
@@ -226,7 +226,7 @@ public class SystemAppUpdateTrackerTest {
assertTrue(!mSystemAppUpdateTracker.getUpdatedApps().contains(DEFAULT_PACKAGE_NAME_2));
// getApplicationLocales should be never be invoked if not a system app.
- verifyZeroInteractions(mMockActivityTaskManager);
+ verifyNoMoreInteractions(mMockActivityTaskManager);
// Broadcast should be never sent if not a system app.
verify(mMockContext, never()).sendBroadcastAsUser(any(), any());
// It shouldn't write to the file if not a system app.
@@ -244,7 +244,7 @@ public class SystemAppUpdateTrackerTest {
Binder.getCallingUid());
// getApplicationLocales should be never be invoked if not installer is not present.
- verifyZeroInteractions(mMockActivityTaskManager);
+ verifyNoMoreInteractions(mMockActivityTaskManager);
// Broadcast should be never sent if installer is not present.
verify(mMockContext, never()).sendBroadcastAsUser(any(), any());
// It shouldn't write to file if no installer present.
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 1de864cb4eb0..4d2dcf65bfeb 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -17,6 +17,7 @@
package com.android.server.location.contexthub;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
@@ -42,12 +43,10 @@ import android.os.Binder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import java.util.Collections;
-import java.util.List;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -57,6 +56,9 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Collections;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@Presubmit
public class ContextHubEndpointTest {
@@ -73,6 +75,16 @@ public class ContextHubEndpointTest {
private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
private static final int TARGET_ENDPOINT_ID = 1;
+ private static final int SAMPLE_MESSAGE_TYPE = 1234;
+ private static final HubMessage SAMPLE_MESSAGE =
+ new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5})
+ .setResponseRequired(true)
+ .build();
+ private static final HubMessage SAMPLE_UNRELIABLE_MESSAGE =
+ new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5})
+ .setResponseRequired(false)
+ .build();
+
private ContextHubClientManager mClientManager;
private ContextHubEndpointManager mEndpointManager;
private HubInfoRegistry mHubInfoRegistry;
@@ -229,23 +241,52 @@ public class ContextHubEndpointTest {
assertThat(mTransactionManager.numReliableMessageTransactionPending()).isEqualTo(0);
}
+ @Test
+ public void testDuplicateMessageRejected() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_MESSAGE);
+ ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class);
+ verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_MESSAGE);
+
+ // Send a duplicate message and confirm it can be rejected
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_MESSAGE);
+ ArgumentCaptor<MessageDeliveryStatus> statusCaptor =
+ ArgumentCaptor.forClass(MessageDeliveryStatus.class);
+ verify(mMockEndpointCommunications)
+ .sendMessageDeliveryStatusToEndpoint(eq(sessionId), statusCaptor.capture());
+ assertThat(statusCaptor.getValue().messageSequenceNumber)
+ .isEqualTo(SAMPLE_MESSAGE.getMessageSequenceNumber());
+ assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.TRANSIENT_ERROR);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testUnreliableMessage() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE);
+ ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class);
+ verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE);
+
+ // Confirm we can send another message
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE);
+ verify(mMockCallback, times(2)).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
/** A helper method to create a session and validates reliable message sending. */
private void testMessageTransactionInternal(
IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException {
- HubEndpointInfo targetInfo =
- new HubEndpointInfo(
- TARGET_ENDPOINT_NAME,
- TARGET_ENDPOINT_ID,
- ENDPOINT_PACKAGE_NAME,
- Collections.emptyList());
- int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
- mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ int sessionId = openTestSession(endpoint);
- final int messageType = 1234;
- HubMessage message =
- new HubMessage.Builder(messageType, new byte[] {1, 2, 3, 4, 5})
- .setResponseRequired(true)
- .build();
IContextHubTransactionCallback callback =
new IContextHubTransactionCallback.Stub() {
@Override
@@ -258,13 +299,13 @@ public class ContextHubEndpointTest {
Log.i(TAG, "Received onTransactionComplete callback, result=" + result);
}
};
- endpoint.sendMessage(sessionId, message, callback);
+ endpoint.sendMessage(sessionId, SAMPLE_MESSAGE, callback);
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mMockEndpointCommunications, timeout(1000))
.sendMessageToEndpoint(eq(sessionId), messageCaptor.capture());
Message halMessage = messageCaptor.getValue();
- assertThat(halMessage.type).isEqualTo(message.getMessageType());
- assertThat(halMessage.content).isEqualTo(message.getMessageBody());
+ assertThat(halMessage.type).isEqualTo(SAMPLE_MESSAGE.getMessageType());
+ assertThat(halMessage.content).isEqualTo(SAMPLE_MESSAGE.getMessageBody());
assertThat(mTransactionManager.numReliableMessageTransactionPending()).isEqualTo(1);
if (deliverMessageStatus) {
@@ -308,4 +349,16 @@ public class ContextHubEndpointTest {
.isEqualTo(expectedInfo.getIdentifier().getHub());
assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
}
+
+ private int openTestSession(IContextHubEndpoint endpoint) throws RemoteException {
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+ mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ return sessionId;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 2da2f50447c7..e836780b3f71 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,6 +16,7 @@
package com.android.server.locksettings;
+import static android.content.pm.UserInfo.FLAG_FOR_TESTING;
import static android.content.pm.UserInfo.FLAG_FULL;
import static android.content.pm.UserInfo.FLAG_MAIN;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
@@ -44,6 +45,8 @@ import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
import android.app.admin.PasswordMetrics;
+import android.content.ComponentName;
+import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -357,6 +360,45 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
}
@Test
+ public void testEscrowDataRetainedWhenManagedUserVerifiesCredential() throws RemoteException {
+ when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(true);
+
+ LockscreenCredential password = newPassword("password");
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
+
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
+
+ assertTrue("Escrow data was destroyed", mSpManager.hasEscrowData(PRIMARY_USER_ID));
+ }
+
+ @Test
+ public void testEscrowDataRetainedWhenUnmanagedTestUserVerifiesCredential()
+ throws RemoteException {
+ when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(false);
+ UserInfo userInfo = mUserManagerInternal.getUserInfo(PRIMARY_USER_ID);
+ userInfo.flags |= FLAG_FOR_TESTING;
+
+ LockscreenCredential password = newPassword("password");
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
+
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
+
+ assertTrue("Escrow data was destroyed", mSpManager.hasEscrowData(PRIMARY_USER_ID));
+ }
+
+ @Test
+ public void testEscrowDataDeletedWhenUnmanagedUserVerifiesCredential() throws RemoteException {
+ when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(false);
+
+ LockscreenCredential password = newPassword("password");
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
+
+ mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
+
+ assertFalse("Escrow data wasn't destroyed", mSpManager.hasAnyEscrowData(PRIMARY_USER_ID));
+ }
+
+ @Test
public void testTokenBasedClearPassword() throws RemoteException {
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS
index bb487fb52c9f..c8b8e4887b5e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS
@@ -1,4 +1,3 @@
aseemk@google.com
-bozhu@google.com
dementyev@google.com
robertberry@google.com
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index a58a9cd2a28f..4a05ea68daec 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -48,7 +48,7 @@ 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.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -488,7 +488,7 @@ public class MediaProjectionManagerServiceTest {
projection.stop(StopReason.STOP_UNKNOWN);
- verifyZeroInteractions(mMediaProjectionMetricsLogger);
+ verifyNoMoreInteractions(mMediaProjectionMetricsLogger);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 570256bf43e6..a46fc2206cf5 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -35,7 +35,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index da14e451d656..03ba3b655fa8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -25,11 +25,11 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index bbc2cb23b269..242ebc550d16 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -59,10 +59,10 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitOnMainThread;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index f5690b77d2fe..46c532bc40fa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -23,9 +23,9 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index 19c26f0b60ab..88395a4889c4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -21,10 +21,10 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Matchers.notNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
index ee1bf38a6b24..7ea33ad1184b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
@@ -18,8 +18,8 @@ package com.android.server.pm;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index f9946604ad5d..b842d3a42f26 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -389,6 +389,44 @@ public final class UserManagerTest {
}
@Test
+ public void testSupervisingProfile() throws Exception {
+ assumeTrue("Device doesn't support supervising profiles ",
+ mUserManager.isUserTypeEnabled(UserManager.USER_TYPE_PROFILE_SUPERVISING));
+
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_SUPERVISING);
+ assertWithMessage("No supervising user type on device").that(userTypeDetails).isNotNull();
+
+
+ // Create supervising profile if it doesn't exist
+ UserInfo supervisingUser = getSupervisingProfile();
+ if (supervisingUser == null) {
+ supervisingUser = createUser("Supervising",
+ UserManager.USER_TYPE_PROFILE_SUPERVISING, /*flags*/ 0);
+ }
+ assertWithMessage("Couldn't create supervising profile").that(supervisingUser).isNotNull();
+ UserHandle supervisingHandle = supervisingUser.getUserHandle();
+
+ // Test that only one supervising profile can be created
+ final UserInfo secondSupervisingProfile =
+ createUser("Supervising", UserManager.USER_TYPE_PROFILE_SUPERVISING,
+ /*flags*/ 0);
+ assertThat(secondSupervisingProfile).isNull();
+
+ // Verify that the supervising profile doesn't have a parent
+ assertThat(mUserManager.getProfileParent(supervisingHandle.getIdentifier())).isNull();
+
+ // Make sure that the supervising profile can be started in the background, and that it
+ // is visible
+ final boolean isStarted = mActivityManager.startProfile(supervisingHandle);
+ assertWithMessage("Unable to start supervising profile").that(isStarted).isTrue();
+ final UserManager umSupervising = (UserManager) mContext.createPackageContextAsUser(
+ "android", 0, supervisingHandle).getSystemService(Context.USER_SERVICE);
+ assertWithMessage("Supervising profile not visible").that(
+ umSupervising.isUserVisible()).isTrue();
+ }
+
+ @Test
public void testGetProfileAccessibilityString_throwsExceptionForNonProfileUser() {
UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
assertThat(user1).isNotNull();
@@ -2198,4 +2236,13 @@ public final class UserManagerTest {
assertEquals(actual.getLevel(), expected.getLevel());
}
+ @Nullable
+ private UserInfo getSupervisingProfile() {
+ for (UserInfo user : mUserManager.getUsers()) {
+ if (user.userType.equals(UserManager.USER_TYPE_PROFILE_SUPERVISING)) {
+ return user;
+ }
+ }
+ return null;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java
index d55f96782084..2ed27048c288 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java
@@ -23,7 +23,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
@@ -273,7 +273,7 @@ public class DynamicCodeLoggerTests {
assertThat(mMessagesForUid).isEmpty();
assertThat(mWriteTriggered).isFalse();
- verifyZeroInteractions(mPM);
+ verifyNoMoreInteractions(mPM);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
index 3551af83aa47..5e5be12665d6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
@@ -17,10 +17,10 @@
package com.android.server.pm.permission;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index 2a4c3fdf8f43..64f88192a0c5 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -21,8 +21,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
diff --git a/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java
index 3cdf1098622a..1b93d4a36ec7 100644
--- a/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java
@@ -48,10 +48,10 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
index b647b99df894..4a97b4670289 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
@@ -18,11 +18,11 @@ package com.android.server.storage;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 9a7abd43aab6..15a60dc600e5 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -62,9 +62,9 @@ import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.intThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
diff --git a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
index 4eb2474da740..770dfe3666b0 100644
--- a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
@@ -20,8 +20,8 @@ import static com.android.server.utils.PriorityDump.dump;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertSame;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.verify;
import android.platform.test.annotations.Presubmit;
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index bf99b6af2345..3dcd1d7b7da5 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -43,7 +43,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
@@ -340,7 +340,7 @@ public class WebViewUpdateServiceTest {
runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
- Matchers.anyObject());
+ ArgumentMatchers.any());
WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
@@ -378,7 +378,7 @@ public class WebViewUpdateServiceTest {
runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
- Matchers.anyObject());
+ ArgumentMatchers.any());
WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
@@ -822,7 +822,7 @@ public class WebViewUpdateServiceTest {
checkPreparationPhasesForPackage(firstPackage, 1);
Mockito.verify(mTestSystemImpl, Mockito.never()).killPackageDependents(
- Mockito.anyObject());
+ Mockito.any());
}
@Test
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 4c0361d30d67..cdc28a13fc8e 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -25,10 +25,10 @@ import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
diff --git a/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java b/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
index c8afb78bc12f..630a7e47fa48 100644
--- a/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
+++ b/services/tests/timetests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
@@ -23,7 +23,7 @@ import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
@@ -126,7 +126,7 @@ public final class GnssTimeUpdateServiceTest {
locationListener.onLocationChanged(location);
verify(mMockLocationManager).removeUpdates(locationListener);
- verifyZeroInteractions(mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockTimeDetectorInternal);
verify(mMockAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
anyLong(),
@@ -150,7 +150,7 @@ public final class GnssTimeUpdateServiceTest {
// Verify the service returned to location listening.
verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
- verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
}
// Tests what happens when a call is made to startGnssListeningInternal() when service is
@@ -172,7 +172,7 @@ public final class GnssTimeUpdateServiceTest {
// listening again.
verify(mMockAlarmManager).cancel(alarmListenerCaptor.getValue());
verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
- verifyZeroInteractions(mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockTimeDetectorInternal);
}
private void advanceServiceToSleepingState(
@@ -190,7 +190,7 @@ public final class GnssTimeUpdateServiceTest {
any(), any(), any(), locationListenerCaptor.capture());
LocationListener locationListener = locationListenerCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER);
- verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
+ verifyNoMoreInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
locationListener.onLocationChanged(location);
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 66d7611a29c6..d34d74d80882 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -11,13 +11,8 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-android_test {
- name: "FrameworksUiServicesTests",
-
- // Include test java files
- srcs: [
- "src/**/*.java",
- ],
+java_defaults {
+ name: "FrameworksUiServicesTests-defaults",
static_libs: [
"compatibility-device-util-axt-minus-dexmaker",
@@ -95,12 +90,72 @@ android_test {
javacflags: ["-parameters"],
}
-test_module_config {
- name: "FrameworksUiServicesTests_notification",
- base: "FrameworksUiServicesTests",
- test_suites: [
- "automotive-tests",
- "device-tests",
+// Utility files used by multiple tests
+filegroup {
+ name: "shared-srcs",
+ srcs: [
+ "src/android/app/ExampleActivity.java",
+ "src/android/app/NotificationSystemUtil.java",
+ "src/com/android/frameworks/tests/uiservices/DummyProvider.java",
+ "src/com/android/internal/logging/InstanceIdSequenceFake.java",
+ "src/com/android/server/UiServiceTestCase.java",
+ "src/com/android/server/notification/ZenChangeOrigin.java",
+ "src/com/android/server/notification/ZenModeEventLoggerFake.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "notification-srcs",
+ srcs: [
+ "src/**/Notification*.java",
+ "src/com/android/server/notification/*.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "notification-zen-srcs",
+ srcs: [
+ "src/android/app/NotificationManagerZenTest.java",
+ "src/com/android/server/notification/Zen*Test.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+android_test {
+ name: "FrameworksUiServicesTests",
+
+ // Include test java files but not the notification & zen ones which are separated
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ exclude_srcs: [
+ ":notification-srcs",
+ ":notification-zen-srcs",
+ ],
+
+ defaults: ["FrameworksUiServicesTests-defaults"],
+}
+
+android_test {
+ name: "FrameworksUiServicesNotificationTests",
+ srcs: [
+ ":notification-srcs",
+ ":shared-srcs",
+ ],
+ exclude_srcs: [":notification-zen-srcs"],
+ defaults: ["FrameworksUiServicesTests-defaults"],
+ test_config: "notification-tests.xml",
+}
+
+android_test {
+ name: "FrameworksUiServicesZenTests",
+ srcs: [
+ ":notification-zen-srcs",
+ ":shared-srcs",
],
- exclude_annotations: ["androidx.test.filters.LargeTest"],
+ defaults: ["FrameworksUiServicesTests-defaults"],
+ test_config: "notification-zen-tests.xml",
}
diff --git a/services/tests/uiservicestests/AndroidTest.xml b/services/tests/uiservicestests/AndroidTest.xml
index 11e8f090a8fa..93c8c72630f1 100644
--- a/services/tests/uiservicestests/AndroidTest.xml
+++ b/services/tests/uiservicestests/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Runs Frameworks UI Services Tests.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
<option name="test-file-name" value="FrameworksUiServicesTests.apk" />
</target_preparer>
diff --git a/services/tests/uiservicestests/notification-tests.xml b/services/tests/uiservicestests/notification-tests.xml
new file mode 100644
index 000000000000..acfd844efe26
--- /dev/null
+++ b/services/tests/uiservicestests/notification-tests.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Frameworks UI Services Tests (notifications subset).">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="FrameworksUiServicesNotificationTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="FrameworksUiServicesNotificationTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.tests.uiservices" />
+ <option name="runner" value="android.testing.TestableInstrumentation" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/uiservicestests/notification-zen-tests.xml b/services/tests/uiservicestests/notification-zen-tests.xml
new file mode 100644
index 000000000000..01d8aab83d59
--- /dev/null
+++ b/services/tests/uiservicestests/notification-zen-tests.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Frameworks UI Services Tests (zen mode subset).">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="FrameworksUiServicesZenTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="FrameworksUiServicesZenTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.tests.uiservices" />
+ <option name="runner" value="android.testing.TestableInstrumentation" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 9930c9f07ed8..7b1ce446da7c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -62,7 +62,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -992,7 +991,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
() -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME));
verify(mContext, never()).enforceCallingPermission(
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
- verifyZeroInteractions(mBinder);
+ verifyNoMoreInteractions(mBinder);
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
}
@@ -1008,7 +1007,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
() -> mService.requestProjection(mBinder, multipleProjectionTypes, PACKAGE_NAME));
verify(mContext, never()).enforceCallingPermission(
eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
- verifyZeroInteractions(mBinder);
+ verifyNoMoreInteractions(mBinder);
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
index 6b989cb0aaee..b33233107766 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -38,7 +38,6 @@ import android.os.IInterface;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.Condition;
-import com.android.internal.R;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
@@ -47,8 +46,6 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
-
public class ConditionProvidersTest extends UiServiceTestCase {
private ConditionProviders mProviders;
@@ -172,15 +169,4 @@ public class ConditionProvidersTest extends UiServiceTestCase {
assertTrue(mProviders.getApproved(userId, true).isEmpty());
}
-
- @Test
- public void getDefaultDndAccessPackages_returnsPackages() {
- mContext.getOrCreateTestableResources().addOverride(
- R.string.config_defaultDndAccessPackages,
- "com.example.a:com.example.b::::com.example.c");
-
- List<String> packages = ConditionProviders.getDefaultDndAccessPackages(mContext);
-
- assertThat(packages).containsExactly("com.example.a", "com.example.b", "com.example.c");
- }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 5ce9a3e8d4d4..8023bdd08927 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -36,7 +36,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -184,7 +183,7 @@ public class DefaultDeviceEffectsApplierTest {
mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
- verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
+ verifyNoMoreInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
}
@Test
@@ -252,8 +251,8 @@ public class DefaultDeviceEffectsApplierTest {
// Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
- verifyZeroInteractions(mPowerManager);
- verifyZeroInteractions(mUiModeManager);
+ verifyNoMoreInteractions(mPowerManager);
+ verifyNoMoreInteractions(mUiModeManager);
}
@Test
@@ -269,7 +268,7 @@ public class DefaultDeviceEffectsApplierTest {
ORIGIN_APP);
// Effect was not yet applied, but a broadcast receiver was registered.
- verifyZeroInteractions(mUiModeManager);
+ verifyNoMoreInteractions(mUiModeManager);
verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(),
intentFilterCaptor.capture(), anyInt());
assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF);
@@ -337,7 +336,7 @@ public class DefaultDeviceEffectsApplierTest {
origin.value());
// Effect was not applied, will be on next screen-off.
- verifyZeroInteractions(mUiModeManager);
+ verifyNoMoreInteractions(mUiModeManager);
verify(mContext).registerReceiver(any(),
argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))),
anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 4eac1d126202..11143653a75d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -50,9 +50,9 @@ import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -60,7 +60,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
@@ -102,7 +102,9 @@ import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
@@ -311,7 +313,7 @@ public class GroupHelperTest extends UiServiceTestCase {
getNotificationRecord(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
false);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -325,7 +327,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
mGroupHelper.onNotificationPosted(
getNotificationRecord(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -338,7 +340,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
mGroupHelper.onNotificationPosted(
getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -351,7 +353,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(
getNotificationRecord(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a", false),
false);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1742,7 +1744,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1757,7 +1759,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1773,7 +1775,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1789,7 +1791,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1809,7 +1811,7 @@ public class GroupHelperTest extends UiServiceTestCase {
String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, "testGrp", true);
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1828,7 +1830,7 @@ public class GroupHelperTest extends UiServiceTestCase {
String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.of(7), "testGrp", true);
notificationList.add(r);
mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1851,7 +1853,7 @@ public class GroupHelperTest extends UiServiceTestCase {
String.valueOf(AUTOGROUP_AT_COUNT + 1), UserHandle.SYSTEM, "testGrp", false);
notificationList.add(child);
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -1875,7 +1877,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(child);
summaryByGroup.put(summary.getGroupKey(), summary);
mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -2207,7 +2209,7 @@ public class GroupHelperTest extends UiServiceTestCase {
childrenToRemove.add(child);
}
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
// Remove all child notifications from the valid group => summary without children
Mockito.reset(mCallback);
@@ -2271,7 +2273,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
}
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
// Remove some child notifications from the valid group, transform into a singleton group
Mockito.reset(mCallback);
@@ -2327,7 +2329,7 @@ public class GroupHelperTest extends UiServiceTestCase {
notificationList.add(child);
}
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
// Remove all child notifications from the valid group => summary without children
Mockito.reset(mCallback);
@@ -2341,7 +2343,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
summaryByGroup);
// Check that nothing was force grouped
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -3835,7 +3837,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
}
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@@ -3859,7 +3861,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
}
// FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS is disabled => don't force group
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
@Test
@@ -4443,4 +4445,97 @@ public class GroupHelperTest extends UiServiceTestCase {
verify(mCallback).sendAppProvidedSummaryDeleteIntent(eq(pkg),
eq(deleteIntentofFirstSummary));
}
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testGroupSummaryAdded_hadUngroupedNotif_doesNotAutogroup() {
+ // Scenario:
+ // * child notification posted before summary; added to ungrouped notifications
+ // * summary posted, so now the child has a group summary and is no longer "ungrouped
+ // * another ungrouped notification is posted
+ // Confirm that the first notification (that now has a summary) is not autogrouped.
+
+ // Bookkeeping items
+ List<NotificationRecord> notifList = new ArrayList<>();
+ Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>();
+
+ // Setup: post AUTOGROUP_AT_COUNT - 2 notifications so that the next notification would not
+ // trigger autogrouping, but the one after that would
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 2; i++) {
+ NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, "group" + i, false,
+ IMPORTANCE_DEFAULT);
+ notifList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey);
+ }
+
+ // Group child: posted enough before its associated summary to be put in the "ungrouped"
+ // set of notifications
+ NotificationRecord groupChild = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT - 2, "",
+ mUser, "specialGroup", false, IMPORTANCE_DEFAULT);
+ notifList.add(groupChild);
+ mGroupHelper.onNotificationPostedWithDelay(groupChild, notifList, summaryByGroupKey);
+
+ // Group summary: posted after child 1
+ NotificationRecord groupSummary = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT - 1, "",
+ mUser, "specialGroup", true, IMPORTANCE_DEFAULT);
+ notifList.add(groupSummary);
+ summaryByGroupKey.put(groupSummary.getSbn().getGroupKey(), groupSummary);
+ mGroupHelper.onGroupSummaryAdded(groupSummary, notifList);
+ mGroupHelper.onNotificationPostedWithDelay(groupSummary, notifList, summaryByGroupKey);
+
+ // One more notification posted to the group; because its summary already exists, it should
+ // never be counted as an "ungrouped" notification
+ NotificationRecord groupChild2 = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT, "",
+ mUser, "specialGroup", false, IMPORTANCE_DEFAULT);
+ notifList.add(groupChild2);
+ mGroupHelper.onNotificationPostedWithDelay(groupChild2, notifList, summaryByGroupKey);
+
+ // Now one more ungrouped notification; this would have put the number of "ungrouped"
+ // notifications above the limit if the first groupChild notification were left ungrouped
+ NotificationRecord extra = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT + 1, "", mUser,
+ "yetAnotherGroup", false, IMPORTANCE_DEFAULT);
+ notifList.add(extra);
+ mGroupHelper.onNotificationPostedWithDelay(extra, notifList, summaryByGroupKey);
+
+ // no autogrouping should have occurred
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testGroupSummaryAdded_onlyUnrelatedGroupedNotifs() {
+ // If all of the existing ungrouped notifications have nothing to do with the summary
+ // they should still get grouped as needed.
+ List<NotificationRecord> notifList = new ArrayList<>();
+ Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>();
+
+ // Post 1 fewer than the autogroupable notifications, each associated with a different
+ // group without a summary.
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, "group" + i, false,
+ IMPORTANCE_DEFAULT);
+ notifList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey);
+ }
+
+ // At this point we do not yet expect autogrouping.
+ // Add a group summary that is a summary associated with none of the above notifications.
+ // Because this gets considered a "summary without children", all of these notifications
+ // should now be autogrouped.
+ NotificationRecord summary = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT, "", mUser,
+ "summaryGroup", true, IMPORTANCE_DEFAULT);
+ notifList.add(summary);
+ summaryByGroupKey.put(summary.getSbn().getKey(), summary);
+ mGroupHelper.onGroupSummaryAdded(summary, notifList);
+ mGroupHelper.onNotificationPostedWithDelay(summary, notifList, summaryByGroupKey);
+
+ // all of the above posted notifications should be autogrouped
+ String expectedGroupKey = getExpectedAutogroupKey(
+ getNotificationRecord(mPkg, 0, String.valueOf(0), mUser));
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(mPkg), anyString(), eq(expectedGroupKey),
+ anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), anyBoolean());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 98440ecdad82..9c85b04fc4ff 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -40,10 +40,10 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 076e3e9fcc24..aef3d30dba93 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -36,8 +36,8 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index bc01fc4f29c0..8fbd3bda98dd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -48,7 +48,6 @@ 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.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -1114,8 +1113,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyDelayedVibrate(
mAttentionHelper.getVibratorHelper().createFallbackVibration(
/* insistent= */ false));
- verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(),
- anyObject(), anyFloat());
+ verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(),
+ any(), anyFloat());
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index 41011928f8b3..8de2b9c6d3a6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -32,9 +32,9 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 5aecac2cab78..9e8023ff344f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -23,9 +23,9 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3727bbefb1d3..bc8b7becc919 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -168,10 +168,10 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.atLeastOnce;
@@ -5211,41 +5211,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void
- updateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm()
- throws Exception {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(mPkg, mUserId))
- .thenReturn(singletonList(mock(AssociationInfo.class)));
- when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
- eq(mTestNotificationChannel.getId()), anyBoolean()))
- .thenReturn(mTestNotificationChannel);
-
- // Missing Uri permissions for the old channel sound
- final Uri oldSoundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(Process.myUid()), any(), eq(oldSoundUri),
- anyInt(), eq(Process.myUserHandle().getIdentifier()));
-
- // Has Uri permissions for the old channel sound
- final Uri newSoundUri = Uri.parse("content://media/test/sound/uri");
- final NotificationChannel updatedNotificationChannel = new NotificationChannel(
- TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
- updatedNotificationChannel.setSound(newSoundUri,
- updatedNotificationChannel.getAudioAttributes());
-
- mBinderService.updateNotificationChannelFromPrivilegedListener(
- null, mPkg, Process.myUserHandle(), updatedNotificationChannel);
-
- verify(mPreferencesHelper, times(1)).updateNotificationChannel(
- anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
-
- verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
- eq(Process.myUserHandle()), eq(mTestNotificationChannel),
- eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
- }
-
- @Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 4391152220c0..e9cf036d0fb3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -1009,6 +1009,65 @@ public class NotificationRecordTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ public void testSummarization_null() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertThat(record.getSummarization()).isNull();
+
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, null);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertThat(record.getSummarization()).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ public void testSummarization_charSequence() {
+ CharSequence summary = "hello";
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertThat(record.getSummarization()).isNull();
+
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, summary);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertThat(record.getSummarization()).isEqualTo(summary.toString());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ public void testSummarization_string() {
+ String summary = "hello";
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertThat(record.getSummarization()).isNull();
+
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, summary);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertThat(record.getSummarization()).isEqualTo(summary);
+ }
+
+ @Test
public void testSensitiveContent() {
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 752910d6d3c1..43228f434997 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,13 +46,11 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
-import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
-import static android.content.ContentResolver.SCHEME_CONTENT;
-import static android.content.ContentResolver.SCHEME_FILE;
import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
+
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
@@ -66,6 +64,7 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
@@ -92,13 +91,12 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
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.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
@@ -385,10 +383,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -803,7 +801,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_oldXml_migrates() throws Exception {
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -939,7 +937,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -998,7 +996,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
+ /* showReviewPermissionsNotification= */ false, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1057,7 +1055,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1655,7 +1653,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// simulate load after reboot
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1710,7 +1708,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// simulate load after reboot
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1788,10 +1786,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
mXmlHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -3263,61 +3261,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() {
- final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri");
-
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound),
- anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
-
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
-
- assertThrows(SecurityException.class,
- () -> mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel,
- true, false, UID_N_MR1, false));
- assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true))
- .isNull();
- }
-
- @Test
- public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() {
- final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound");
-
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
- anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
-
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
-
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
- false);
- assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
- .getSound()).isEqualTo(sound);
- }
-
- @Test
- public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() {
- final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound");
-
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
- anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
-
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
-
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
- false);
- assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
- .getSound()).isEqualTo(sound);
- }
-
- @Test
public void testPermanentlyDeleteChannels() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -6429,7 +6372,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel same = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_P, 0, same, true, false, 0, false);
- verifyZeroInteractions(mHandler);
+ verifyNoMoreInteractions(mHandler);
}
@Test
@@ -6455,7 +6398,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.updateNotificationChannel(PKG_P, 0, same, false, 0, false);
- verifyZeroInteractions(mHandler);
+ verifyNoMoreInteractions(mHandler);
}
@Test
@@ -6469,7 +6412,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void setShowBadge_same_doesNotRequestSort() {
mHelper.setShowBadge(PKG_P, 0, true); // true == DEFAULT_SHOW_BADGE
- verifyZeroInteractions(mHandler);
+ verifyNoMoreInteractions(mHandler);
}
@Test
@@ -7086,10 +7029,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- UriGrantsManagerInternal ugmInternal,
boolean showReviewPermissionsNotification, Clock clock) {
super(context, pm, rankingHandler, zenHelper, permHelper, permManager,
- notificationChannelLogger, appOpsManager, userProfiles, ugmInternal,
+ notificationChannelLogger, appOpsManager, userProfiles,
showReviewPermissionsNotification, clock);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index ec428d506e7b..91e4fd623cd4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -25,8 +25,8 @@ import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 31436c602e56..62f7471597f0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -25,7 +25,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index dcd56e07f0d2..7f05ddefa891 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -28,9 +28,9 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java
deleted file mode 100644
index 154a905c776b..000000000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenConfigTrimmerTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.truth.Truth.assertThat;
-
-import android.os.Parcel;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ZenRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.UiServiceTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class ZenConfigTrimmerTest extends UiServiceTestCase {
-
- private static final String TRUSTED_PACKAGE = "com.trust.me";
- private static final int ONE_PERCENT = 1_500;
-
- private ZenConfigTrimmer mTrimmer;
-
- @Before
- public void setUp() {
- mContext.getOrCreateTestableResources().addOverride(
- R.string.config_defaultDndAccessPackages, TRUSTED_PACKAGE);
-
- mTrimmer = new ZenConfigTrimmer(mContext);
- }
-
- @Test
- public void trimToMaximumSize_belowMax_untouched() {
- ZenModeConfig config = new ZenModeConfig();
- addZenRule(config, "1", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "2", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "3", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "4", "pkg2", 20 * ONE_PERCENT);
- addZenRule(config, "5", "pkg2", 20 * ONE_PERCENT);
-
- mTrimmer.trimToMaximumSize(config);
-
- assertThat(config.automaticRules.keySet()).containsExactly("1", "2", "3", "4", "5");
- }
-
- @Test
- public void trimToMaximumSize_exceedsMax_removesAllRulesOfLargestPackages() {
- ZenModeConfig config = new ZenModeConfig();
- addZenRule(config, "1", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "2", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "3", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "4", "pkg2", 20 * ONE_PERCENT);
- addZenRule(config, "5", "pkg2", 20 * ONE_PERCENT);
- addZenRule(config, "6", "pkg3", 35 * ONE_PERCENT);
- addZenRule(config, "7", "pkg4", 38 * ONE_PERCENT);
-
- mTrimmer.trimToMaximumSize(config);
-
- assertThat(config.automaticRules.keySet()).containsExactly("1", "2", "3", "6");
- assertThat(config.automaticRules.values().stream().map(r -> r.pkg).distinct())
- .containsExactly("pkg1", "pkg3");
- }
-
- @Test
- public void trimToMaximumSize_keepsRulesFromTrustedPackages() {
- ZenModeConfig config = new ZenModeConfig();
- addZenRule(config, "1", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "2", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "3", "pkg1", 10 * ONE_PERCENT);
- addZenRule(config, "4", TRUSTED_PACKAGE, 60 * ONE_PERCENT);
- addZenRule(config, "5", "pkg2", 20 * ONE_PERCENT);
- addZenRule(config, "6", "pkg3", 35 * ONE_PERCENT);
-
- mTrimmer.trimToMaximumSize(config);
-
- assertThat(config.automaticRules.keySet()).containsExactly("4", "5");
- assertThat(config.automaticRules.values().stream().map(r -> r.pkg).distinct())
- .containsExactly(TRUSTED_PACKAGE, "pkg2");
- }
-
- /**
- * Create a ZenRule that, when serialized to a Parcel, will take <em>approximately</em>
- * {@code desiredSize} bytes (within 100 bytes). Try to make the tests not rely on a very tight
- * fit.
- */
- private static void addZenRule(ZenModeConfig config, String id, String pkg, int desiredSize) {
- ZenRule rule = new ZenRule();
- rule.id = id;
- rule.pkg = pkg;
- config.automaticRules.put(id, rule);
-
- // Make the ZenRule as large as desired. Not to the exact byte, because otherwise this
- // test would have to be adjusted whenever we change the parceling of ZenRule in any way.
- // (Still might need adjustment if we change the serialization _significantly_).
- int nameLength = desiredSize - id.length() - pkg.length() - 232;
- rule.name = "A".repeat(nameLength);
-
- Parcel verification = Parcel.obtain();
- try {
- verification.writeParcelable(rule, 0);
- assertThat(verification.dataSize()).isWithin(100).of(desiredSize);
- } finally {
- verification.recycle();
- }
- }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 51891ef71bbd..bfce647356ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -90,7 +90,6 @@ import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
-import static com.android.server.notification.Flags.FLAG_LIMIT_ZEN_CONFIG_SIZE;
import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING;
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
@@ -203,6 +202,7 @@ import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -237,7 +237,6 @@ import java.util.stream.Collectors;
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWith(ParameterizedAndroidJunit4.class)
-@EnableFlags(FLAG_LIMIT_ZEN_CONFIG_SIZE) // Should be parameterization, but off path does nothing.
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
@@ -2478,6 +2477,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @Ignore("TODO: b/398023814 - disabled due to taking a long time; restore when we have a "
+ + "better approach to not timing out")
public void testAddAutomaticZenRule_claimedSystemOwner() {
// Make sure anything that claims to have a "system" owner but not actually part of the
// system package still gets limited on number of rules
@@ -7482,45 +7483,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(getZenRule(ruleId).lastActivation).isNull();
}
- @Test
- @EnableFlags(FLAG_LIMIT_ZEN_CONFIG_SIZE)
- public void addAutomaticZenRule_trimsConfiguration() {
- mZenModeHelper.mConfig.automaticRules.clear();
- AutomaticZenRule smallRule = new AutomaticZenRule.Builder("Reasonable", CONDITION_ID)
- .setConfigurationActivity(new ComponentName(mPkg, "cls"))
- .build();
- AutomaticZenRule systemRule = new AutomaticZenRule.Builder("System", CONDITION_ID)
- .setOwner(new ComponentName("android", "ScheduleConditionProvider"))
- .build();
-
- AutomaticZenRule bigRule = new AutomaticZenRule.Builder("Yuge", CONDITION_ID)
- .setConfigurationActivity(new ComponentName("evil.package", "cls"))
- .setTriggerDescription("0123456789".repeat(6000)) // ~60k bytes utf16.
- .build();
-
- String systemRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android",
- systemRule, ORIGIN_SYSTEM, "add", SYSTEM_UID);
- String smallRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, smallRule,
- ORIGIN_APP, "add", CUSTOM_PKG_UID);
- String bigRuleId1 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "evil.package",
- bigRule, ORIGIN_APP, "add", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
- systemRuleId, smallRuleId, bigRuleId1);
-
- String bigRuleId2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "evil.package",
- bigRule, ORIGIN_APP, "add", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
- systemRuleId, smallRuleId, bigRuleId1, bigRuleId2);
-
- // This should go over the threshold
- String bigRuleId3 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "evil.package",
- bigRule, ORIGIN_APP, "add", CUSTOM_PKG_UID);
-
- // Rules from evil.package are gone.
- assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
- systemRuleId, smallRuleId);
- }
-
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 04335df8c454..2baf0c141c89 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -247,6 +247,7 @@ public class VibrationThreadTest {
assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_singleVibratorWaveform_runsVibrationAndChangesAmplitudes() {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -269,7 +270,10 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags({
+ Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
+ Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING,
+ })
public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
// No user settings scale.
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -296,7 +300,10 @@ public class VibrationThreadTest {
}
@Test
- @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags({
+ Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
+ Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING,
+ })
public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
// No user settings scale.
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -357,6 +364,7 @@ public class VibrationThreadTest {
}
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
throws Exception {
@@ -380,6 +388,7 @@ public class VibrationThreadTest {
.containsExactly(expectedOneShot(5000)).inOrder();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_singleVibratorPatternWithZeroDurationSteps_skipsZeroDurationSteps() {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -399,6 +408,7 @@ public class VibrationThreadTest {
.containsExactlyElementsIn(expectedOneShots(100L, 150L)).inOrder();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_singleVibratorPatternWithZeroDurationAndAmplitude_skipsZeroDurationSteps() {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -537,6 +547,7 @@ public class VibrationThreadTest {
assertThat(fakeVibrator.getEffectSegments(vibration.id)).hasSize(10);
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
throws Exception {
@@ -700,6 +711,7 @@ public class VibrationThreadTest {
.containsExactly(expectedPrebaked(VibrationEffect.EFFECT_THUD)).inOrder();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_singleVibratorPrebakedAndUnsupportedEffectWithFallback_runsFallback() {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -1247,6 +1259,7 @@ public class VibrationThreadTest {
.containsExactly(expected).inOrder();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_multipleStereo_runsVibrationOnRightVibrators() {
mockVibrators(1, 2, 3, 4);
@@ -1479,6 +1492,7 @@ public class VibrationThreadTest {
assertThat(mVibratorProviders.get(1).getAmplitudes()).isEmpty();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_multipleWaveforms_playsWaveformsInParallel() throws Exception {
mockVibrators(1, 2, 3);
@@ -1544,8 +1558,8 @@ public class VibrationThreadTest {
VibrationEffect.createOneShot(
expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
- startThreadAndDispatcher(vibration);
long startTime = SystemClock.elapsedRealtime();
+ startThreadAndDispatcher(vibration);
vibration.waitForEnd();
long vibrationEndTime = SystemClock.elapsedRealtime();
@@ -1554,15 +1568,13 @@ public class VibrationThreadTest {
long completionTime = SystemClock.elapsedRealtime();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
- // Vibration ends after duration, thread completed after ramp down
- assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration);
+ // Vibration ends before ramp down, thread completed after ramp down
assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration);
assertThat(completionTime - startTime).isAtLeast(expectedDuration + rampDownDuration);
}
@Test
- public void vibrate_withVibratorCallbackDelayShorterThanTimeout_vibrationFinishedAfterDelay()
- throws Exception {
+ public void vibrate_withVibratorCallbackDelayShorterThanTimeout_vibrationFinishedAfterDelay() {
long expectedDuration = 10;
long callbackDelay = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT / 2;
@@ -1577,10 +1589,8 @@ public class VibrationThreadTest {
long startTime = SystemClock.elapsedRealtime();
startThreadAndDispatcher(vibration);
- vibration.waitForEnd();
- long vibrationEndTime = SystemClock.elapsedRealtime();
-
waitForCompletion(TEST_TIMEOUT_MILLIS);
+ long vibrationEndTime = SystemClock.elapsedRealtime();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay);
@@ -1588,8 +1598,7 @@ public class VibrationThreadTest {
@LargeTest
@Test
- public void vibrate_withVibratorCallbackDelayLongerThanTimeout_vibrationFinishedAfterTimeout()
- throws Exception {
+ public void vibrate_withVibratorCallbackDelayLongerThanTimeout_vibrationFinishedAfterTimeout() {
long expectedDuration = 10;
long callbackTimeout = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
long callbackDelay = callbackTimeout * 2;
@@ -1602,21 +1611,17 @@ public class VibrationThreadTest {
VibrationEffect.createOneShot(
expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
- startThreadAndDispatcher(vibration);
long startTime = SystemClock.elapsedRealtime();
-
- vibration.waitForEnd();
- long vibrationEndTime = SystemClock.elapsedRealtime();
+ startThreadAndDispatcher(vibration);
waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
- long completionTime = SystemClock.elapsedRealtime();
+ long vibrationEndTime = SystemClock.elapsedRealtime();
verify(mControllerCallbacks, never())
.onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
// Vibration ends and thread completes after timeout, before the HAL callback
assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout);
assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay);
- assertThat(completionTime - startTime).isLessThan(expectedDuration + callbackDelay);
}
@LargeTest
@@ -1637,8 +1642,8 @@ public class VibrationThreadTest {
Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE);
VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1);
- startThreadAndDispatcher(effect);
long startTime = SystemClock.elapsedRealtime();
+ startThreadAndDispatcher(effect);
waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS);
long delay = Math.abs(SystemClock.elapsedRealtime() - startTime - totalDuration);
@@ -1806,6 +1811,7 @@ public class VibrationThreadTest {
assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
@@ -1833,6 +1839,7 @@ public class VibrationThreadTest {
}
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds()
throws Exception {
@@ -1863,6 +1870,7 @@ public class VibrationThreadTest {
verify(mManagerHooks).onVibrationThreadReleased(vibration.id);
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled()
throws Exception {
@@ -2057,6 +2065,7 @@ public class VibrationThreadTest {
.containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
}
+ @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
@Test
public void vibrate_multipleVibratorsSequentialInSession_runsInOrderWithoutDelaysAndNoOffs() {
mockVibrators(1, 2, 3);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 79e272b7ec01..01698b5bdd6b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -133,7 +132,7 @@ public class VibratorControlServiceTest {
mVibratorControlService.registerVibratorController(controller1);
mVibratorControlService.unregisterVibratorController(controller2);
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
assertThat(controller1.isLinkedToDeath).isTrue();
}
@@ -187,7 +186,7 @@ public class VibratorControlServiceTest {
verify(mStatsLoggerMock).logVibrationParamResponseIgnored();
verifyNoMoreInteractions(mStatsLoggerMock);
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test(expected = IllegalArgumentException.class)
@@ -242,7 +241,7 @@ public class VibratorControlServiceTest {
mFakeVibratorController);
verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test(expected = IllegalArgumentException.class)
@@ -280,7 +279,7 @@ public class VibratorControlServiceTest {
mFakeVibratorController);
verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
- verifyZeroInteractions(mMockVibrationScaler);
+ verifyNoMoreInteractions(mMockVibrationScaler);
}
@Test
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
index 9a59ede20e23..011971d85f42 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger_middleware.RecognitionEventSys;
@@ -76,7 +75,7 @@ public class SoundTriggerHalConcurrentCaptureHandlerTest {
assertEquals(event.halEventReceivedMillis, -1);
assertEquals(event.recognitionEvent.status, RecognitionStatus.ABORTED);
assertFalse(event.recognitionEvent.recognitionStillActive);
- verifyZeroInteractions(mGlobalCallback);
+ verifyNoMoreInteractions(mGlobalCallback);
clearInvocations(callback, mUnderlying);
mNotifier.setActive(false);
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 6d8a48799112..fcdf88f16550 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -150,8 +150,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
META_ON},
- {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON},
{"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
KeyEvent.KEYCODE_APP_SWITCH, 0},
@@ -607,29 +605,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
- public void testKeyGestureAccessibilityShortcutChord() {
- Assert.assertTrue(
- sendKeyGestureEventStart(
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
- mPhoneWindowManager.moveTimeForward(5000);
- Assert.assertTrue(
- sendKeyGestureEventCancel(
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
- mPhoneWindowManager.assertAccessibilityKeychordCalled();
- }
-
- @Test
- public void testKeyGestureAccessibilityShortcutChordCancelled() {
- Assert.assertTrue(
- sendKeyGestureEventStart(
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
- Assert.assertTrue(
- sendKeyGestureEventCancel(
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD));
- mPhoneWindowManager.assertAccessibilityKeychordNotCalled();
- }
-
- @Test
public void testKeyGestureRingerToggleChord() {
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
Assert.assertTrue(
@@ -672,29 +647,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
- public void testKeyGestureAccessibilityTvShortcutChord() {
- Assert.assertTrue(
- sendKeyGestureEventStart(
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
- mPhoneWindowManager.moveTimeForward(5000);
- Assert.assertTrue(
- sendKeyGestureEventCancel(
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
- mPhoneWindowManager.assertAccessibilityKeychordCalled();
- }
-
- @Test
- public void testKeyGestureAccessibilityTvShortcutChordCancelled() {
- Assert.assertTrue(
- sendKeyGestureEventStart(
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
- Assert.assertTrue(
- sendKeyGestureEventCancel(
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD));
- mPhoneWindowManager.assertAccessibilityKeychordNotCalled();
- }
-
- @Test
public void testKeyGestureTvTriggerBugReport() {
Assert.assertTrue(
sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index d961a6acc9c1..50fc77676958 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -21,7 +21,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.junit.Assert.assertEquals;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
@@ -90,7 +90,7 @@ public class ModifierShortcutManagerTests {
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
- doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt());
+ doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
@@ -106,7 +106,7 @@ public class ModifierShortcutManagerTests {
doReturn(testActivityInfo).when(mPackageManager).getActivityInfo(
eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt());
- doReturn(testResolveInfo).when(mPackageManager).resolveActivity(anyObject(), anyInt());
+ doReturn(testResolveInfo).when(mPackageManager).resolveActivity(any(), anyInt());
doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager)
.getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")),
anyInt());
@@ -140,10 +140,10 @@ public class ModifierShortcutManagerTests {
public void test_shortcutInfoFromIntent_appIntent() {
Intent mockIntent = mock(Intent.class);
ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
- when(mockActivityInfo.loadLabel(anyObject())).thenReturn("label");
+ when(mockActivityInfo.loadLabel(any())).thenReturn("label");
mockActivityInfo.packageName = "android";
when(mockActivityInfo.getIconResource()).thenReturn(R.drawable.sym_def_app_icon);
- when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+ when(mockIntent.resolveActivityInfo(any(), anyInt())).thenReturn(mockActivityInfo);
KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
'a', mockIntent, true);
@@ -161,7 +161,7 @@ public class ModifierShortcutManagerTests {
Intent mockSelector = mock(Intent.class);
ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
mockActivityInfo.name = com.android.internal.app.ResolverActivity.class.getName();
- when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+ when(mockIntent.resolveActivityInfo(any(), anyInt())).thenReturn(mockActivityInfo);
when(mockIntent.getSelector()).thenReturn(mockSelector);
when(mockSelector.getCategories()).thenReturn(
Collections.singleton(Intent.CATEGORY_APP_BROWSER));
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 8ede9efd32a0..c57adfd69b06 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -44,7 +44,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static java.util.Collections.unmodifiableMap;
@@ -111,7 +111,7 @@ class ShortcutKeyTestBase {
mContext = spy(getInstrumentation().getTargetContext());
mResources = spy(mContext.getResources());
mPackageManager = spy(mContext.getPackageManager());
- doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt());
+ doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
doReturn(mResources).when(mContext).getResources();
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index f88492477487..e56fd3c6272d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -750,11 +750,6 @@ class TestPhoneWindowManager {
verify(mAccessibilityShortcutController).performAccessibilityShortcut();
}
- void assertAccessibilityKeychordNotCalled() {
- mTestLooper.dispatchAll();
- verify(mAccessibilityShortcutController, never()).performAccessibilityShortcut();
- }
-
void assertCloseAllDialogs() {
verify(mContext).closeSystemDialogs();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 39a849a22622..7f242dea9f45 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -724,6 +724,7 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientation_allowFixedOrientationForCameraCompatInFreeformWindowing() {
final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
@@ -2020,8 +2021,6 @@ public class ActivityRecordTests extends WindowTestsBase {
display.setFixedRotationLaunchingAppUnchecked(activity);
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
- assertTrue(displayRotation.isRotatingSeamlessly());
-
// The launching rotated app should not be cleared when waiting for remote rotation.
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 948371f74a9c..ad706e879b72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -63,11 +63,23 @@ public class ActivitySnapshotControllerTests extends TaskSnapshotPersisterTestBa
super(0.8f /* highResScale */, 0.5f /* lowResScale */);
}
+ private class TestActivitySnapshotController extends ActivitySnapshotController {
+ TestActivitySnapshotController(WindowManagerService service,
+ SnapshotPersistQueue persistQueue) {
+ super(service, persistQueue);
+ }
+ @Override
+ BaseAppSnapshotPersister.PersistInfoProvider createPersistInfoProvider(
+ WindowManagerService service) {
+ return mPersister.mPersistInfoProvider;
+ }
+ }
@Override
@Before
public void setUp() {
super.setUp();
- mActivitySnapshotController = new ActivitySnapshotController(mWm, mSnapshotPersistQueue);
+ mActivitySnapshotController = new TestActivitySnapshotController(
+ mWm, mSnapshotPersistQueue);
spyOn(mActivitySnapshotController);
doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots();
mActivitySnapshotController.resetTmpFields();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e3e9cc426bb3..08b0077c49b3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -797,7 +797,7 @@ public class ActivityStarterTests extends WindowTestsBase {
// Create adjacent tasks and put one activity under it
final Task parent = new TaskBuilder(mSupervisor).build();
final Task adjacentParent = new TaskBuilder(mSupervisor).build();
- parent.setAdjacentTaskFragment(adjacentParent);
+ parent.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(parent, adjacentParent));
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setParentTask(parent)
.setCreateTask(true).build();
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 a9be47d71213..862ce51d3459 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -75,13 +76,15 @@ import java.util.concurrent.TimeUnit;
* Tests for the {@link ActivityTaskSupervisor} class.
*
* Build/Install/Run:
- * atest WmTests:ActivityTaskSupervisorTests
+ * atest WmTests:ActivityTaskSupervisorTests
*/
@MediumTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityTaskSupervisorTests extends WindowTestsBase {
private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final int DEFAULT_CALLING_PID = -1;
+ private static final int DEFAULT_CALLING_UID = -1;
/**
* Ensures that an activity is removed from the stopping activities list once it is resumed.
@@ -110,7 +113,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
.setCreateTask(true).build();
final ConditionVariable condition = new ConditionVariable();
final WaitResult taskToFrontWait = new WaitResult();
- final ComponentName[] launchedComponent = { null };
+ final ComponentName[] launchedComponent = {null};
// Create a new thread so the waiting method in test can be notified.
new Thread(() -> {
synchronized (mAtm.mGlobalLock) {
@@ -408,7 +411,8 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
anyInt(), any());
- mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
assertThat(activity.mLaunchCookie).isEqualTo(launchCookie);
verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
@@ -426,12 +430,62 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
anyInt(), any());
- mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
assertThat(activity.mLaunchCookie).isNull();
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_inMultiWindowRootTask_homeNotMoved() {
+ final Task multiWindowRootTask = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW).setOnTop(true).build();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setParentTask(
+ multiWindowRootTask).setCreateTask(true).build();
+
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ ActivityOptions.makeBasic().toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
+
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ verify(mRootWindowContainer.getDefaultTaskDisplayArea(), never()).moveHomeRootTaskToFront(
+ any());
+ verify(multiWindowRootTask.getDisplayArea(), never()).moveHomeRootTaskToFront(any());
+ }
+
+ /** Verifies that launch from recents doesn't set the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_inFullScreenRootTask_homeMovedToFront() {
+ final Task fullscreenRootTask = new TaskBuilder(mSupervisor).setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).setOnTop(true).build();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setParentTask(
+ fullscreenRootTask).setCreateTask(true).build();
+
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ ActivityOptions.makeBasic().toBundle(),
+ Binder.getCallingPid(), Binder.getCallingUid());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(DEFAULT_CALLING_PID, DEFAULT_CALLING_UID,
+ activity.getRootTaskId(), safeOptions);
+
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ verify(mRootWindowContainer.getDefaultTaskDisplayArea()).moveHomeRootTaskToFront(any());
+ verify(fullscreenRootTask.getDisplayArea()).moveHomeRootTaskToFront(any());
+ }
+
@Test
public void testOpaque_leafTask_occludingActivity_isOpaque() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -488,14 +542,13 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask,
WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
- tf1.setAdjacentTaskFragment(tf2);
+ tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2));
assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue();
}
@Test
- @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
- Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS})
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() {
final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index d016e735f0c7..cb98b9a490d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -243,6 +243,11 @@ class AppCompatActivityRobot {
.getAspectRatioOverrides()).getUserMinAspectRatio();
}
+ void setShouldRefreshActivityForCameraCompat(boolean enabled) {
+ doReturn(enabled).when(mActivityStack.top().mAppCompatController.getCameraOverrides())
+ .shouldRefreshActivityForCameraCompat();
+ }
+
void setIgnoreOrientationRequest(boolean enabled) {
mDisplayContent.setIgnoreOrientationRequest(enabled);
}
@@ -251,10 +256,6 @@ class AppCompatActivityRobot {
doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask();
}
- void setIsInLetterboxAnimation(boolean inAnimation) {
- doReturn(inAnimation).when(mActivityStack.top()).isInLetterboxAnimation();
- }
-
void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 05f6ed644632..7ef85262dfc2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -64,6 +64,19 @@ class AppCompatConfigurationRobot {
.isCameraCompatTreatmentEnabledAtBuildTime();
}
+ void setCameraCompatAspectRatio(float aspectRatio) {
+ doReturn(aspectRatio).when(mAppCompatConfiguration).getCameraCompatAspectRatio();
+ }
+
+ void enableCameraCompatRefresh(boolean enabled) {
+ doReturn(enabled).when(mAppCompatConfiguration).isCameraCompatRefreshEnabled();
+ }
+
+ void enableCameraCompatRefreshCycleThroughStop(boolean enabled) {
+ doReturn(enabled).when(mAppCompatConfiguration)
+ .isCameraCompatRefreshCycleThroughStopEnabled();
+ }
+
void enableUserAppAspectRatioFullscreen(boolean enabled) {
doReturn(enabled).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
index d38f3b09c4fa..7bcf4eaa7c8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
@@ -66,7 +66,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -87,7 +86,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ false);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -109,7 +107,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -131,7 +128,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -157,7 +153,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
robot.conf().setLetterboxActivityCornersRadius(-1);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -184,25 +179,17 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
robot.setInvCompatState(/* scale */ 0.5f);
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ true);
- robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
-
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
robot.activity().setTopActivityVisibleRequested(/* isVisibleRequested */ false);
robot.activity().setTopActivityVisible(/* isVisible */ false);
robot.checkWindowStateRoundedCornersRadius(/* expected */ 0);
-
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ true);
- robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
});
}
@@ -212,7 +199,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -236,7 +222,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -260,7 +245,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -362,10 +346,6 @@ public class AppCompatLetterboxPolicyTest extends WindowTestsBase {
mWindowState.mInvGlobalScale = scale;
}
- void setTopActivityInLetterboxAnimation(boolean inLetterboxAnimation) {
- doReturn(inLetterboxAnimation).when(activity().top()).isInLetterboxAnimation();
- }
-
void setTopActivityTransparentPolicyRunning(boolean running) {
doReturn(running).when(getTransparentPolicy()).isRunning();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
index a4a63d266725..ac707d23b492 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
@@ -63,25 +63,10 @@ public class AppCompatLetterboxUtilsTest extends WindowTestsBase {
}
@Test
- public void positionIsFromTaskWhenLetterboxAnimationIsRunning() {
+ public void positionIsFromActivity() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponent();
robot.setTopActivityLetterboxPolicyRunning(true);
- robot.activity().setIsInLetterboxAnimation(true);
- robot.activity().configureTaskBounds(
- new Rect(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ 400));
- robot.getLetterboxPosition();
-
- robot.assertPosition(/* x */ 100, /* y */ 200);
- });
- }
-
- @Test
- public void positionIsFromActivityWhenLetterboxAnimationIsNotRunning() {
- runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
- robot.setTopActivityLetterboxPolicyRunning(true);
- robot.activity().setIsInLetterboxAnimation(false);
robot.activity().configureTopActivityBounds(
new Rect(/* left */ 200, /* top */ 400, /* right */ 300, /* bottom */ 400));
robot.getLetterboxPosition();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java
new file mode 100644
index 000000000000..bdd57bce7c09
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatSafeRegionPolicyTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatSafeRegionPolicy}.
+ * Build/Install/Run:
+ * atest WmTests:AppCompatSafeRegionPolicyTests
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+@EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+public class AppCompatSafeRegionPolicyTests extends WindowTestsBase {
+
+ @Test
+ public void testHasNeedsSafeRegion_returnTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ true);
+
+ robot.checkNeedsSafeRegionBounds(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testDoesNotHaveNeedsSafeRegion_returnFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.setNeedsSafeRegionBounds(/*needsSafeRegionBounds*/ false);
+
+ robot.checkNeedsSafeRegionBounds(/* expected */ false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<AppCompatSafeRegionPolicyRobotTest> consumer) {
+ final AppCompatSafeRegionPolicyRobotTest robot =
+ new AppCompatSafeRegionPolicyRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class AppCompatSafeRegionPolicyRobotTest extends AppCompatRobotBase {
+
+ AppCompatSafeRegionPolicyRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getSafeRegionPolicy());
+ }
+
+ AppCompatSafeRegionPolicy getAppCompatSafeRegionPolicy() {
+ return activity().top().mAppCompatController.getSafeRegionPolicy();
+ }
+
+ void setNeedsSafeRegionBounds(boolean needsSafeRegionBounds) {
+ doReturn(needsSafeRegionBounds).when(
+ getAppCompatSafeRegionPolicy()).getNeedsSafeRegionBounds();
+ }
+
+ void checkNeedsSafeRegionBounds(boolean expected) {
+ assertEquals(expected, getAppCompatSafeRegionPolicy().getNeedsSafeRegionBounds());
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index bfd533aa8f79..b2cfbbd23b22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -98,6 +98,24 @@ public class AppCompatUtilsTest extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void getLetterboxReasonString_isLetterboxedForSafeRegionOnly() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+ /* forFixedOrientationAndAspectRatio */ false);
+ robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
+ robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false);
+ robot.setIsLetterboxedForSafeRegionOnlyAllowed(/* safeRegionOnly */ true);
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "SAFE_REGION");
+ });
+ }
+
+ @Test
public void getLetterboxReasonString_aspectRatio() {
runTestScenario((robot) -> {
robot.applyOnActivity((a) -> {
@@ -124,6 +142,7 @@ public class AppCompatUtilsTest extends WindowTestsBase {
/* forFixedOrientationAndAspectRatio */ false);
robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false);
+ robot.setIsLetterboxedForSafeRegionOnlyAllowed(/* safeRegionOnly */ false);
robot.checkTopActivityLetterboxReason(/* expected */ "UNKNOWN_REASON");
});
@@ -253,6 +272,7 @@ public class AppCompatUtilsTest extends WindowTestsBase {
void onPostActivityCreation(@NonNull ActivityRecord activity) {
super.onPostActivityCreation(activity);
spyOn(activity.mAppCompatController.getAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getSafeRegionPolicy());
}
@Override
@@ -286,6 +306,11 @@ public class AppCompatUtilsTest extends WindowTestsBase {
when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout);
}
+ void setIsLetterboxedForSafeRegionOnlyAllowed(boolean safeRegionOnly) {
+ when(activity().top().mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed()).thenReturn(safeRegionOnly);
+ }
+
void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) {
doReturn(mode).when(activity().top().mDisplayContent.mAppCompatCameraPolicy
.mCameraCompatFreeformPolicy).getCameraCompatMode(activity().top());
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 e08197155f03..dd3e9fcbbdaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -225,10 +225,10 @@ public class BackNavigationControllerTests extends WindowTestsBase {
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
testCase.windowFront.mAttrs.windowAnimations = 0x10;
- spyOn(mDisplayContent.mAppTransition.mTransitionAnimation);
- doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+ spyOn(mDisplayContent.mTransitionAnimation);
+ doReturn(0xffff00AB).when(mDisplayContent.mTransitionAnimation)
.getAnimationResId(any(), anyInt(), anyInt());
- doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+ doReturn(0xffff00CD).when(mDisplayContent.mTransitionAnimation)
.getDefaultAnimationResId(anyInt(), anyInt());
BackNavigationInfo backNavigationInfo = startBackNavigation();
@@ -343,8 +343,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
// Adjacent + no companion => unable to predict
// TF1 | TF2
- tf1.setAdjacentTaskFragment(tf2);
- tf2.setAdjacentTaskFragment(tf1);
+ tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2));
predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
outPrevActivities);
assertTrue(outPrevActivities.isEmpty());
@@ -393,8 +392,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
// Adjacent => predict for previous activity.
// TF2 | TF3
// TF1
- tf2.setAdjacentTaskFragment(tf3);
- tf3.setAdjacentTaskFragment(tf2);
+ tf2.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf2, tf3));
predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
outPrevActivities);
assertTrue(outPrevActivities.contains(prevAr));
@@ -657,8 +655,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord primaryActivity = primaryTf.getTopMostActivity();
final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity();
- primaryTf.setAdjacentTaskFragment(secondaryTf);
- secondaryTf.setAdjacentTaskFragment(primaryTf);
+ primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf));
final WindowState primaryWindow = mock(WindowState.class);
final WindowState secondaryWindow = mock(WindowState.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index f5bec04a98d5..6f959812d742 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -21,13 +21,13 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
+import static android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
@@ -40,18 +40,15 @@ import static android.view.Surface.ROTATION_90;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -59,13 +56,11 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
-import android.app.CameraCompatTaskInfo;
import android.app.IApplicationThread;
import android.app.WindowConfiguration.WindowingMode;
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.compat.testing.PlatformCompatChangeRule;
-import android.content.ComponentName;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -73,17 +68,16 @@ import android.content.res.Configuration.Orientation;
import android.graphics.Rect;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
-import android.view.DisplayInfo;
import android.view.Surface;
import androidx.test.filters.SmallTest;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -91,6 +85,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Tests for {@link CameraCompatFreeformPolicy}.
@@ -109,30 +104,18 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests";
private static final String TEST_PACKAGE_2 = "com.test.package.two";
private static final String CAMERA_ID_1 = "camera-1";
- private AppCompatConfiguration mAppCompatConfiguration;
-
- private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
- private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
- private ActivityRecord mActivity;
-
- // TODO(b/384465100): use a robot structure.
- @Before
- public void setUp() throws Exception {
- setupAppCompatConfiguration();
- setupCameraManager();
- setupHandler();
- doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
- }
@Test
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testFeatureDisabled_cameraCompatFreeformPolicyNotCreated() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertNull(mCameraCompatFreeformPolicy);
+ robot.checkCameraCompatPolicyNotCreated();
+ });
}
@Test
@@ -140,31 +123,37 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
public void testIsCameraRunningAndWindowingModeEligible_disabledViaOverride_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ robot.checkIsCameraRunningAndWindowingModeEligible(false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsCameraRunningAndWindowingModeEligible_cameraNotRunning_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ robot.checkIsCameraRunningAndWindowingModeEligible(false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ robot.checkIsCameraRunningAndWindowingModeEligible(false);
+ });
}
@Test
@@ -172,64 +161,76 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ robot.checkIsCameraRunningAndWindowingModeEligible(true);
+ });
}
@Test
@EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
public void testIsCameraRunningAndWindowingModeEligible_freeformCameraRunning_true() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ robot.checkIsCameraRunningAndWindowingModeEligible(true);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+ robot.checkIsFreeformLetterboxingForCameraAllowed(false);
+ });
}
@Test
@EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
public void testIsFreeformLetterboxingForCameraAllowed_notOptedOut_returnsTrue() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+ robot.checkIsFreeformLetterboxingForCameraAllowed(true);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsFreeformLetterboxingForCameraAllowed_cameraNotRunning_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+ robot.checkIsFreeformLetterboxingForCameraAllowed(false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsFreeformLetterboxingForCameraAllowed_notFreeformWindowing_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+ robot.checkIsFreeformLetterboxingForCameraAllowed(false);
+ });
}
@Test
@@ -237,519 +238,603 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+ robot.checkIsFreeformLetterboxingForCameraAllowed(true);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testFullscreen_doesNotActivateCameraCompatMode() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- doReturn(false).when(mActivity).inFreeformWindowingMode();
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+ robot.setInFreeformWindowingMode(false);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertNotInCameraCompatMode();
+ robot.assertNotInCameraCompatMode();
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientationUnspecified_doesNotActivateCameraCompatMode() {
- configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
- assertNotInCameraCompatMode();
+ robot.assertNotInCameraCompatMode();
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testNoCameraConnection_doesNotActivateCameraCompatMode() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertNotInCameraCompatMode();
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ robot.assertNotInCameraCompatMode();
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(ROTATION_0);
+ public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_0);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
- assertActivityRefreshRequested(/* refreshRequested */ false);
+ robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(ROTATION_270);
+ public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_270);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
- assertActivityRefreshRequested(/* refreshRequested */ false);
+ robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
- configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(ROTATION_0);
+ public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_0);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
- assertActivityRefreshRequested(/* refreshRequested */ false);
+ robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
- configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(ROTATION_270);
+ public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_270);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
- assertActivityRefreshRequested(/* refreshRequested */ false);
+ robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(ROTATION_270);
+ public void testCameraReconnected_cameraCompatModeAndRefresh() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_270);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true,
/* lastLetterbox= */ false);
- assertActivityRefreshRequested(/* refreshRequested */ true);
- onCameraClosed(CAMERA_ID_1);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- // Activity is letterboxed from the previous configuration change.
- callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
- /* lastLetterbox= */ true);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ true);
+ robot.onCameraClosed(CAMERA_ID_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ // Activity is letterboxed from the previous configuration change.
+ robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true,
+ /* lastLetterbox= */ true);
- assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
- assertActivityRefreshRequested(/* refreshRequested */ true);
+ robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ true);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
- assertNotInCameraCompatMode();
+ robot.assertNotInCameraCompatMode();
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
- /* checkOrientation */ true));
+ robot.checkIsCameraCompatTreatmentActiveForTopActivity(false);
+ });
}
@Test
@EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_returnsTrue() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
- /* checkOrientation */ true));
+ robot.checkIsCameraCompatTreatmentActiveForTopActivity(true);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mActivity.info
- .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
- assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
- /* checkOrientation */ true));
+ robot.checkIsCameraCompatTreatmentActiveForTopActivity(true);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- Configuration oldConfiguration = createConfiguration(/* letterbox= */ false);
- Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
- oldConfiguration));
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ robot.checkShouldRefreshActivity(/* expected= */ true,
+ robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0),
+ robot.createConfiguration(/* letterbox= */ false, /* rotation= */ 0));
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
- Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- oldConfiguration.windowConfiguration.setDisplayRotation(0);
- newConfiguration.windowConfiguration.setDisplayRotation(90);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
- oldConfiguration));
+ robot.checkShouldRefreshActivity(/* expected= */ true,
+ robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 90),
+ robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0));
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
- Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- oldConfiguration.windowConfiguration.setDisplayRotation(0);
- newConfiguration.windowConfiguration.setDisplayRotation(0);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
- oldConfiguration));
+ robot.checkShouldRefreshActivity(/* expected= */ false,
+ robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0),
+ robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0));
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
- throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-
- doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
- .shouldRefreshActivityForCameraCompat();
+ public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.activity().setShouldRefreshActivityForCameraCompat(false);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging();
- assertActivityRefreshRequested(/* refreshRequested */ false);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
- when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
- .thenReturn(false);
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.conf().enableCameraCompatRefreshCycleThroughStop(false);
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging();
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
-
- assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ true,
+ /* cycleThroughStop */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
- throws Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
- .shouldRefreshActivityViaPauseForCameraCompat();
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.setShouldRefreshActivityViaPause(true);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging();
- assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ robot.assertActivityRefreshRequested(/* refreshRequested */ true,
+ /* cycleThroughStop */ false);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
- configureActivity(SCREEN_ORIENTATION_FULL_USER);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_FULL_USER);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging();
- assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
- mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
- /* delta= */ 0.001);
+ robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- final float configAspectRatio = 1.5f;
- mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final float configAspectRatio = 1.5f;
+ robot.conf().setCameraCompatAspectRatio(configAspectRatio);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging();
- assertEquals(configAspectRatio,
- mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
- /* delta= */ 0.001);
+ robot.checkCameraCompatAspectRatioEquals(configAspectRatio);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- final float configAspectRatio = 1.5f;
- mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
- doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
- .isOverrideMinAspectRatioForCameraEnabled();
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.conf().setCameraCompatAspectRatio(1.5f);
+ robot.setOverrideMinAspectRatioEnabled(true);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- callOnActivityConfigurationChanging(mActivity);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.callOnActivityConfigurationChanging();
- assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
- mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
- /* delta= */ 0.001);
+ robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() throws
- Exception {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(ROTATION_270);
+ public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_270);
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- // This is a portrait rotation for a device with portrait natural orientation (most common,
- // currently the only one supported).
- assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0);
+ // This is a portrait rotation for a device with portrait natural orientation (most
+ // common, currently the only one supported).
+ robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0);
+ });
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() throws
- Exception {
- configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(ROTATION_0);
-
- onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-
- // This is a landscape rotation for a device with portrait natural orientation (most common,
- // currently the only one supported).
- assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90);
- }
-
- private void setupAppCompatConfiguration() {
- mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration;
- spyOn(mAppCompatConfiguration);
- when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true);
- when(mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime()).thenReturn(true);
- when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true);
- when(mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled())
- .thenReturn(false);
- when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
- .thenReturn(true);
- }
-
- private void setupCameraManager() {
- final CameraManager mockCameraManager = mock(CameraManager.class);
- doAnswer(invocation -> {
- mCameraAvailabilityCallback = invocation.getArgument(1);
- return null;
- }).when(mockCameraManager).registerAvailabilityCallback(
- any(Executor.class), any(CameraManager.AvailabilityCallback.class));
-
- when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager);
- }
-
- private void setupHandler() {
- final Handler handler = mDisplayContent.mWmService.mH;
- spyOn(handler);
-
- when(handler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
- invocation -> {
- ((Runnable) invocation.getArgument(0)).run();
- return null;
- });
- }
-
- private void configureActivity(@ScreenOrientation int activityOrientation) {
- configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
- }
-
- private void configureActivity(@ScreenOrientation int activityOrientation,
- @WindowingMode int windowingMode) {
- configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode);
- }
-
- private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
- @Orientation int naturalOrientation, @WindowingMode int windowingMode) {
- setupDisplayContent(naturalOrientation);
- final Task task = setupTask(windowingMode);
- setupActivity(task, activityOrientation, windowingMode);
- setupMockApplicationThread();
-
- mCameraCompatFreeformPolicy = mDisplayContent.mAppCompatCameraPolicy
- .mCameraCompatFreeformPolicy;
- }
-
- private void setupDisplayContent(@Orientation int naturalOrientation) {
- // Create a new DisplayContent so that the flag values create the camera freeform policy.
- mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayContent.getSurfaceWidth(),
- mDisplayContent.getSurfaceHeight()).build();
- mDisplayContent.setIgnoreOrientationRequest(true);
- setDisplayRotation(ROTATION_90);
- doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
- }
-
- private Task setupTask(@WindowingMode int windowingMode) {
- final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
- spyOn(tda);
- doReturn(true).when(tda).supportsNonResizableMultiWindow();
-
- final Task task = new TaskBuilder(mSupervisor)
- .setDisplay(mDisplayContent)
- .setWindowingMode(windowingMode)
- .build();
- task.setBounds(0, 0, 1000, 500);
- return task;
- }
-
- private void setupActivity(@NonNull Task task, @ScreenOrientation int activityOrientation,
- @WindowingMode int windowingMode) {
- mActivity = new ActivityBuilder(mAtm)
- // Set the component to be that of the test class in order to enable compat changes
- .setComponent(ComponentName.createRelative(mContext,
- com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName()))
- .setScreenOrientation(activityOrientation)
- .setResizeMode(RESIZE_MODE_RESIZEABLE)
- .setCreateTask(true)
- .setOnTop(true)
- .setTask(task)
- .build();
- mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
-
- spyOn(mActivity.mAppCompatController.getCameraOverrides());
- spyOn(mActivity.info);
-
- doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
- doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity)
- .inFreeformWindowingMode();
- }
-
- private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
- mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName);
- waitHandlerIdle(mDisplayContent.mWmService.mH);
- }
-
- private void onCameraClosed(@NonNull String cameraId) {
- mCameraAvailabilityCallback.onCameraClosed(cameraId);
- waitHandlerIdle(mDisplayContent.mWmService.mH);
- }
-
- private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
- assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity));
- }
-
- private void assertNotInCameraCompatMode() {
- assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE);
- }
-
- private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
- assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
- }
-
- private void assertActivityRefreshRequested(boolean refreshRequested,
- boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getCameraOverrides(),
- times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
-
- final RefreshCallbackItem refreshCallbackItem =
- new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
- /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
-
- verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
- .scheduleTransactionItems(mActivity.app.getThread(),
- refreshCallbackItem, resumeActivityItem);
- }
-
- private void callOnActivityConfigurationChanging(ActivityRecord activity) {
- callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true,
- /* lastLetterbox= */false);
- }
-
- private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew,
- boolean lastLetterbox) {
- mDisplayContent.mAppCompatCameraPolicy.mActivityRefresher
- .onActivityConfigurationChanging(activity,
- /* newConfig */ createConfiguration(letterboxNew),
- /* lastReportedConfig */ createConfiguration(lastLetterbox));
- }
-
- private Configuration createConfiguration(boolean letterbox) {
- final Configuration configuration = new Configuration();
- Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600)
- : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600);
- configuration.windowConfiguration.setAppBounds(bounds);
- return configuration;
- }
-
- private void setDisplayRotation(@Surface.Rotation int displayRotation) {
- doAnswer(invocation -> {
- DisplayInfo displayInfo = new DisplayInfo();
- mDisplayContent.getDisplay().getDisplayInfo(displayInfo);
- displayInfo.rotation = displayRotation;
- // Set height so that the natural orientation (rotation is 0) is portrait. This is the
- // case for most standard phones and tablets.
- // TODO(b/365725400): handle landscape natural orientation.
- displayInfo.logicalHeight = displayRotation % 180 == 0 ? 800 : 600;
- displayInfo.logicalWidth = displayRotation % 180 == 0 ? 600 : 800;
- return displayInfo;
- }).when(mDisplayContent.mWmService.mDisplayManagerInternal)
- .getDisplayInfo(anyInt());
- }
-
- private void setupMockApplicationThread() {
- IApplicationThread mockApplicationThread = mock(IApplicationThread.class);
- spyOn(mActivity.app);
- doReturn(mockApplicationThread).when(mActivity.app).getThread();
- }
-
- private void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int
- expectedRotation) throws Exception {
- final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor =
- ArgumentCaptor.forClass(CompatibilityInfo.class);
- verify(mActivity.app.getThread()).updatePackageCompatibilityInfo(eq(mActivity.packageName),
- compatibilityInfoArgumentCaptor.capture());
-
- final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue();
- assertTrue(compatInfo.isOverrideDisplayRotationRequired());
- assertEquals(expectedRotation, compatInfo.applicationDisplayRotation);
+ public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+ robot.activity().rotateDisplayForTopActivity(ROTATION_0);
+
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ // This is a landscape rotation for a device with portrait natural orientation (most
+ // common, currently the only one supported).
+ robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<CameraCompatFreeformPolicyRobotTests> consumer) {
+ final CameraCompatFreeformPolicyRobotTests robot =
+ new CameraCompatFreeformPolicyRobotTests(mWm, mAtm, mSupervisor, this);
+ consumer.accept(robot);
+ }
+
+ private static class CameraCompatFreeformPolicyRobotTests extends AppCompatRobotBase {
+ private final WindowTestsBase mWindowTestsBase;
+
+ private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
+
+ CameraCompatFreeformPolicyRobotTests(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor,
+ @NonNull WindowTestsBase windowTestsBase) {
+ super(wm, atm, supervisor);
+ mWindowTestsBase = windowTestsBase;
+ setupCameraManager();
+ setupAppCompatConfiguration();
+ }
+
+ @Override
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ super.onPostDisplayContentCreation(displayContent);
+ spyOn(displayContent.mAppCompatCameraPolicy);
+ if (displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy != null) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ setupCameraManager();
+ setupHandler();
+ setupMockApplicationThread();
+ }
+
+ private void setupMockApplicationThread() {
+ IApplicationThread mockApplicationThread = mock(IApplicationThread.class);
+ spyOn(activity().top().app);
+ doReturn(mockApplicationThread).when(activity().top().app).getThread();
+ }
+
+ private Configuration createConfiguration(boolean letterbox, int rotation) {
+ final Configuration configuration = createConfiguration(letterbox);
+ configuration.windowConfiguration.setDisplayRotation(rotation);
+ return configuration;
+ }
+
+ private Configuration createConfiguration(boolean letterbox) {
+ final Configuration configuration = new Configuration();
+ Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/
+ 600)
+ : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600);
+ configuration.windowConfiguration.setAppBounds(bounds);
+ return configuration;
+ }
+
+ private void setupAppCompatConfiguration() {
+ applyOnConf((c) -> {
+ c.enableCameraCompatTreatment(true);
+ c.enableCameraCompatTreatmentAtBuildTime(true);
+ c.enableCameraCompatRefresh(true);
+ c.enableCameraCompatRefreshCycleThroughStop(true);
+ c.enableCameraCompatSplitScreenAspectRatio(false);
+ });
+ }
+
+ private void setupCameraManager() {
+ final CameraManager mockCameraManager = mock(CameraManager.class);
+ doAnswer(invocation -> {
+ mCameraAvailabilityCallback = invocation.getArgument(1);
+ return null;
+ }).when(mockCameraManager).registerAvailabilityCallback(
+ any(Executor.class), any(CameraManager.AvailabilityCallback.class));
+
+ doReturn(mockCameraManager).when(mWindowTestsBase.mWm.mContext).getSystemService(
+ CameraManager.class);
+ }
+
+ private void setupHandler() {
+ final Handler handler = activity().top().mWmService.mH;
+ spyOn(handler);
+
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(handler).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ private void configureActivity(@ScreenOrientation int activityOrientation) {
+ configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
+ }
+
+ private void configureActivity(@ScreenOrientation int activityOrientation,
+ @WindowingMode int windowingMode) {
+ configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode);
+ }
+
+ private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
+ @Orientation int naturalOrientation, @WindowingMode int windowingMode) {
+ applyOnActivity(a -> {
+ dw().allowEnterDesktopMode(true);
+ a.createActivityWithComponentInNewTaskAndDisplay();
+ a.setIgnoreOrientationRequest(true);
+ a.rotateDisplayForTopActivity(ROTATION_90);
+ a.configureTopActivity(/* minAspect */ -1, /* maxAspect */ -1,
+ activityOrientation, /* isUnresizable */ false);
+ a.top().setWindowingMode(windowingMode);
+ a.displayContent().setWindowingMode(windowingMode);
+ a.setDisplayNaturalOrientation(naturalOrientation);
+ spyOn(a.top().mAppCompatController.getCameraOverrides());
+ spyOn(a.top().info);
+ doReturn(a.displayContent().getDisplayInfo()).when(
+ a.displayContent().mWmService.mDisplayManagerInternal).getDisplayInfo(
+ a.displayContent().mDisplayId);
+ });
+ }
+
+ private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
+ mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName);
+ waitHandlerIdle();
+ }
+
+ private void onCameraClosed(@NonNull String cameraId) {
+ mCameraAvailabilityCallback.onCameraClosed(cameraId);
+ }
+
+ private void waitHandlerIdle() {
+ mWindowTestsBase.waitHandlerIdle(activity().displayContent().mWmService.mH);
+ }
+
+ void setInFreeformWindowingMode(boolean inFreeform) {
+ doReturn(inFreeform).when(activity().top()).inFreeformWindowingMode();
+ }
+
+ void setShouldRefreshActivityViaPause(boolean enabled) {
+ doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides())
+ .shouldRefreshActivityViaPauseForCameraCompat();
+ }
+
+ void checkShouldRefreshActivity(boolean expected, Configuration newConfig,
+ Configuration oldConfig) {
+ assertEquals(expected, cameraCompatFreeformPolicy().shouldRefreshActivity(
+ activity().top(), newConfig, oldConfig));
+ }
+
+ void checkCameraCompatPolicyNotCreated() {
+ assertNull(cameraCompatFreeformPolicy());
+ }
+
+ void checkIsCameraRunningAndWindowingModeEligible(boolean expected) {
+ assertEquals(expected, cameraCompatFreeformPolicy()
+ .isCameraRunningAndWindowingModeEligible(activity().top()));
+ }
+
+ void checkIsFreeformLetterboxingForCameraAllowed(boolean expected) {
+ assertEquals(expected, cameraCompatFreeformPolicy()
+ .isFreeformLetterboxingForCameraAllowed(activity().top()));
+ }
+
+ void checkCameraCompatAspectRatioEquals(float aspectRatio) {
+ assertEquals(aspectRatio,
+ cameraCompatFreeformPolicy().getCameraCompatAspectRatio(activity().top()),
+ /* delta= */ 0.001);
+ }
+
+ private void assertInCameraCompatMode(@FreeformCameraCompatMode int mode) {
+ assertEquals(mode, cameraCompatFreeformPolicy().getCameraCompatMode(activity().top()));
+ }
+
+ private void assertNotInCameraCompatMode() {
+ assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested) {
+ assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested,
+ boolean cycleThroughStop) {
+ verify(activity().top().mAppCompatController.getCameraOverrides(),
+ times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
+
+ final RefreshCallbackItem refreshCallbackItem =
+ new RefreshCallbackItem(activity().top().token,
+ cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
+ activity().top().token,
+ /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ try {
+ verify(activity().top().mAtmService.getLifecycleManager(),
+ times(refreshRequested ? 1 : 0))
+ .scheduleTransactionItems(activity().top().app.getThread(),
+ refreshCallbackItem, resumeActivityItem);
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ private void callOnActivityConfigurationChanging() {
+ callOnActivityConfigurationChanging(/* letterboxNew= */ true,
+ /* lastLetterbox= */false);
+ }
+
+ private void callOnActivityConfigurationChanging(boolean letterboxNew,
+ boolean lastLetterbox) {
+ activity().displayContent().mAppCompatCameraPolicy.mActivityRefresher
+ .onActivityConfigurationChanging(activity().top(),
+ /* newConfig */ createConfiguration(letterboxNew),
+ /* lastReportedConfig */ createConfiguration(lastLetterbox));
+ }
+
+ void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
+ assertEquals(active,
+ cameraCompatFreeformPolicy().isTreatmentEnabledForActivity(activity().top(),
+ /* checkOrientation */ true));
+ }
+
+ void setOverrideMinAspectRatioEnabled(boolean enabled) {
+ doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides())
+ .isOverrideMinAspectRatioForCameraEnabled();
+ }
+
+ void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int
+ expectedRotation) {
+ final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor =
+ ArgumentCaptor.forClass(CompatibilityInfo.class);
+ try {
+ verify(activity().top().app.getThread()).updatePackageCompatibilityInfo(
+ eq(activity().top().packageName),
+ compatibilityInfoArgumentCaptor.capture());
+ } catch (RemoteException e) {
+ fail(e.getMessage());
+ }
+
+ final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue();
+ assertTrue(compatInfo.isOverrideDisplayRotationRequired());
+ assertEquals(expectedRotation, compatInfo.applicationDisplayRotation);
+ }
+
+ CameraCompatFreeformPolicy cameraCompatFreeformPolicy() {
+ return activity().displayContent().mAppCompatCameraPolicy.mCameraCompatFreeformPolicy;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 76b994d013f3..ad76662c6c18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
import static com.google.common.truth.Truth.assertThat;
@@ -51,6 +52,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.media.projection.StopReason;
import android.os.IBinder;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -558,6 +560,22 @@ public class ContentRecorderTests extends WindowTestsBase {
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
}
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testStartRecording_shouldShowSystemDecorations_recordingNotStarted() {
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+
+ spyOn(mVirtualDisplayContent.mDisplay);
+ doReturn(true).when(mVirtualDisplayContent.mDisplay).canHostTasks();
+
+ // WHEN a recording tries to start.
+ mContentRecorder.updateRecording();
+
+ // THEN recording does not start.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
@Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
defaultInit();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
index 1e91bedb5c18..43755ea3165e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -181,6 +181,7 @@ public class DesktopModeHelperTest {
assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
}
+ @DisableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE)
@Test
public void isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() {
doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index bc37496d14a7..00b617e91bfd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE;
@@ -40,6 +41,7 @@ import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx;
import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
import static com.android.server.wm.DesktopModeBoundsCalculator.centerInScreen;
@@ -157,7 +159,7 @@ public class DesktopModeLaunchParamsModifierTests extends
@Test
@EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
- public void testReturnsContinueIfVisibleFreeformTaskExists() {
+ public void testReturnsContinueIfFreeformTaskExists() {
setupDesktopModeLaunchParamsModifier();
when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod();
@@ -165,7 +167,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
doReturn(existingFreeformTask.getRootActivity()).when(dc)
- .getTopMostVisibleFreeformActivity();
+ .getTopMostFreeformActivity();
final Task launchingTask = new TaskBuilder(mSupervisor).build();
launchingTask.onDisplayChanged(dc);
@@ -269,6 +271,38 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES})
+ public void testInheritTaskBoundsFromExistingInstanceIfClosing() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final String packageName = "com.same.package";
+ // Setup existing task.
+ final DisplayContent dc = spy(createNewDisplay());
+ final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).setPackage(packageName).build();
+ existingFreeformTask.setBounds(
+ /* left */ 0,
+ /* top */ 0,
+ /* right */ 500,
+ /* bottom */ 500);
+ doReturn(existingFreeformTask.getRootActivity()).when(dc)
+ .getTopMostVisibleFreeformActivity();
+ // Set up new instance of already existing task. By default multi instance is not supported
+ // so first instance will close.
+ final Task launchingTask = new TaskBuilder(mSupervisor).setPackage(packageName)
+ .setCreateActivity(true).build();
+ launchingTask.onDisplayChanged(dc);
+ launchingTask.intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+
+ // New instance should inherit task bounds of old instance.
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(launchingTask)
+ .setActivity(launchingTask.getRootActivity()).calculate());
+ assertEquals(existingFreeformTask.getBounds(), mResult.mBounds);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() {
@@ -349,7 +383,7 @@ public class DesktopModeLaunchParamsModifierTests extends
spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
mActivity.mAppCompatController.getAspectRatioOverrides())
- .isUserFullscreenOverrideEnabled();
+ .hasFullscreenOverride();
final int desiredWidth =
(int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -377,7 +411,7 @@ public class DesktopModeLaunchParamsModifierTests extends
spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
doReturn(true).when(
mActivity.mAppCompatController.getAspectRatioOverrides())
- .isSystemOverrideToFullscreenEnabled();
+ .hasFullscreenOverride();
final int desiredWidth =
(int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -860,9 +894,11 @@ public class DesktopModeLaunchParamsModifierTests extends
@Test
@EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS,
+ Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS})
public void testDefaultLandscapeBounds_landscapeDevice_unResizable_landscapeOrientation() {
setupDesktopModeLaunchParamsModifier();
+ final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext);
final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
LANDSCAPE_DISPLAY_BOUNDS);
@@ -870,11 +906,11 @@ public class DesktopModeLaunchParamsModifierTests extends
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
task, /* ignoreOrientationRequest */ true);
-
- final int desiredWidth =
- (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final float displayAspectRatio = (float) LANDSCAPE_DISPLAY_BOUNDS.width()
+ / LANDSCAPE_DISPLAY_BOUNDS.height();
final int desiredHeight =
(int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredWidth = (int) ((desiredHeight - captionHeight) * displayAspectRatio);
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
.setActivity(activity).calculate());
@@ -883,7 +919,8 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ @EnableFlags({Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS,
+ Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS})
public void testUnResizablePortraitBounds_landscapeDevice_unResizable_portraitOrientation() {
setupDesktopModeLaunchParamsModifier();
@@ -892,6 +929,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final Task task = createTask(display, /* isResizeable */ false);
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
task, /* ignoreOrientationRequest */ true);
+ final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext);
spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
@@ -899,7 +937,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final int desiredHeight =
(int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- final int desiredWidth = (int) (desiredHeight / LETTERBOX_ASPECT_RATIO);
+ final int desiredWidth = (int) ((desiredHeight - captionHeight) / LETTERBOX_ASPECT_RATIO);
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
.setActivity(activity).calculate());
@@ -1037,7 +1075,8 @@ public class DesktopModeLaunchParamsModifierTests extends
@Test
@EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS,
+ Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS})
public void testDefaultPortraitBounds_portraitDevice_unResizable_portraitOrientation() {
setupDesktopModeLaunchParamsModifier();
@@ -1046,12 +1085,14 @@ public class DesktopModeLaunchParamsModifierTests extends
final Task task = createTask(display, /* isResizeable */ false);
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
task, /* ignoreOrientationRequest */ true);
+ final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext);
-
- final int desiredWidth =
- (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final float displayAspectRatio = (float) PORTRAIT_DISPLAY_BOUNDS.height()
+ / PORTRAIT_DISPLAY_BOUNDS.width();
final int desiredHeight =
- (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredWidth =
+ (int) ((desiredHeight - captionHeight) / displayAspectRatio);
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
.setActivity(activity).calculate());
@@ -1060,7 +1101,8 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ @EnableFlags({Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS,
+ Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS})
public void testUnResizableLandscapeBounds_portraitDevice_unResizable_landscapeOrientation() {
setupDesktopModeLaunchParamsModifier();
@@ -1069,6 +1111,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final Task task = createTask(display, /* isResizeable */ false);
final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
task, /* ignoreOrientationRequest */ true);
+ final int captionHeight = getDesktopViewAppHeaderHeightPx(mContext);
spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
@@ -1076,7 +1119,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
- final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO);
+ final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO) + captionHeight;
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
.setActivity(activity).calculate());
@@ -1127,6 +1170,32 @@ public class DesktopModeLaunchParamsModifierTests extends
@Test
@EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSizeWithFullscreenOverride_noModifications() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ DISPLAY_STABLE_BOUNDS.left,
+ DISPLAY_STABLE_BOUNDS.top,
+ /* right = */ 500,
+ /* bottom = */ 500))
+ .setFlexibleLaunchSize(true);
+ spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAspectRatioOverrides())
+ .hasFullscreenOverride();
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(options.getLaunchBounds(), mResult.mBounds);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
public void testOptionsBoundsSet_flexibleLaunchSize_boundsSizeModified() {
setupDesktopModeLaunchParamsModifier();
@@ -1450,6 +1519,24 @@ public class DesktopModeLaunchParamsModifierTests extends
assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
}
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testFreeformWindowingModeAppliedIfSourceTaskExists() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).build();
+ final Task sourceTask = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ final ActivityRecord sourceActivity = new ActivityBuilder(task.mAtmService)
+ .setTask(sourceTask).build();
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+ .setSource(sourceActivity).calculate());
+ assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+ }
+
private Task createTask(DisplayContent display, Boolean isResizeable) {
final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE
: RESIZE_MODE_UNRESIZEABLE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index a30591ea7b15..9486bc522a9c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -25,6 +25,7 @@ import static com.android.server.wm.utils.LastCallVerifier.lastCall;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -432,4 +433,32 @@ public class DimmerTests extends WindowTestsBase {
verify(mTransaction, never()).setAlpha(dimLayer, 0.5f);
verify(mTransaction).setAlpha(dimLayer, 0.9f);
}
+
+ /**
+ * A window requesting to dim to 0 and without blur would cause the dim to be created and
+ * destroyed continuously.
+ * Ensure the dim layer is not created until the window is requesting valid values.
+ */
+ @Test
+ public void testDimNotCreatedIfNoAlphaNoBlur() {
+ mDimmer.adjustAppearance(mChild1, 0.0f, 0);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ assertNull(mDimmer.getDimLayer());
+ mDimmer.updateDims(mTransaction);
+ assertNull(mDimmer.getDimLayer());
+
+ mDimmer.adjustAppearance(mChild1, 0.9f, 0);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ assertNotNull(mDimmer.getDimLayer());
+ }
+
+ /**
+ * If there is a blur, then the dim layer is created even though alpha is 0
+ */
+ @Test
+ public void testDimCreatedIfNoAlphaButHasBlur() {
+ mDimmer.adjustAppearance(mChild1, 0.0f, 10);
+ mDimmer.adjustPosition(mChild1, mChild1);
+ assertNotNull(mDimmer.getDimLayer());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 0af41ea1f634..89aa3b9a2443 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -56,7 +56,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.ActivityInfo;
@@ -174,7 +174,7 @@ public class DisplayAreaTest extends WindowTestsBase {
da1.reduceOnAllTaskDisplayAreas(callback2, 0);
da1.getItemFromTaskDisplayAreas(callback3);
- verifyZeroInteractions(da2);
+ verifyNoMoreInteractions(da2);
// Traverse the child if the current DA has type ANY
final DisplayArea<WindowContainer> da3 = new DisplayArea<>(mWm, ANY, "DA3");
@@ -207,7 +207,7 @@ public class DisplayAreaTest extends WindowTestsBase {
da5.reduceOnAllTaskDisplayAreas(callback2, 0);
da5.getItemFromTaskDisplayAreas(callback3);
- verifyZeroInteractions(da6);
+ verifyNoMoreInteractions(da6);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java
new file mode 100644
index 000000000000..1445a6982c60
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.window.flags.Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:DisplayCompatTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayCompatTests extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @EnableFlags(FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testFixedMiscConfigurationWhenMovingToDisplay() {
+ // Create an app on the default display, at which point the restart menu isn't enabled.
+ final Task task = createTask(mDefaultDisplay);
+ final ActivityRecord activity = createActivityRecord(task);
+ assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove());
+
+ // Move the app to a secondary display, and the restart menu must get enabled.
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ final DisplayContent secondaryDisplay = createNewDisplay(displayInfo);
+ task.reparent(secondaryDisplay.getDefaultTaskDisplayArea(), true);
+ assertTrue(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove());
+
+ // Once the app gets restarted, the restart menu must be gone.
+ activity.restartProcessIfVisible();
+ assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 7033d79d0ee1..9ce4a80616ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -43,7 +43,6 @@ import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
import com.android.server.wm.TransitionController.OnStartCollect;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -218,7 +217,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testWaitForTransition_displaySwitching_waitsForTransitionToBeStarted() {
- mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
assertThat(willWait).isTrue();
@@ -241,7 +239,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testWaitForTransition_displayNotSwitching_doesNotWait() {
- mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ false);
boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
@@ -251,19 +248,7 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
}
@Test
- public void testWaitForTransition_displayIsSwitchingButFlagDisabled_doesNotWait() {
- mSetFlagsRule.disableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
- mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
-
- boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
-
- assertThat(willWait).isFalse();
- verify(mScreenUnblocker, never()).sendToTarget();
- }
-
- @Test
public void testTwoDisplayUpdateAtTheSameTime_bothDisplaysAreUnblocked() {
- mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
prepareSecondaryDisplay();
final WindowState defaultDisplayWindow = newWindowBuilder("DefaultDisplayWindow",
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 61ed0b53cdcf..ed00a9e8e74b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -30,7 +30,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.view.Display.FLAG_TRUSTED;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.Surface.ROTATION_0;
@@ -87,7 +90,7 @@ import static com.android.server.wm.TransitionSubject.assertThat;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
-import static com.android.window.flags.Flags.FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS;
+import static com.android.window.flags.Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -1706,8 +1709,6 @@ public class DisplayContentTests extends WindowTestsBase {
app.setVisible(true);
doReturn(false).when(app).inTransition();
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
- mStatusBarWindow.finishSeamlessRotation(t);
- mNavBarWindow.finishSeamlessRotation(t);
// The fixed rotation should be cleared and the new rotation is applied to display.
assertFalse(app.hasFixedRotationTransform());
@@ -2631,10 +2632,19 @@ public class DisplayContentTests extends WindowTestsBase {
final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
final BooleanSupplier appVisible = activity::isVisibleRequested;
- // Begin locked and in AOD
+ // Begin unlocked.
+ keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
+ transitions.flush();
+
+ // Lock and go to AOD.
keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+ if (Flags.aodTransition()) {
+ assertThat(transitions.mLastTransit).flags().contains(TRANSIT_FLAG_AOD_APPEARING);
+ } else {
+ assertThat(transitions.mLastTransit).flags().doesNotContain(TRANSIT_FLAG_AOD_APPEARING);
+ }
transitions.flush();
// Start unlocking from AOD.
@@ -2654,14 +2664,7 @@ public class DisplayContentTests extends WindowTestsBase {
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
-
- if (Flags.aodTransition()) {
- assertThat(transitions.mLastTransit).flags()
- .containsExactly(TRANSIT_FLAG_AOD_APPEARING);
- } else {
- assertThat(transitions.mLastTransit).isNull();
- }
- transitions.flush();
+ assertThat(transitions.mLastTransit).isNull();
// Finish unlock
keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
@@ -2684,10 +2687,19 @@ public class DisplayContentTests extends WindowTestsBase {
final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
final BooleanSupplier appVisible = activity::isVisibleRequested;
- // Begin locked and in AOD
+ // Begin unlocked.
+ keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
+ transitions.flush();
+
+ // Lock and go to AOD.
keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+ if (Flags.aodTransition()) {
+ assertThat(transitions.mLastTransit).flags().contains(TRANSIT_FLAG_AOD_APPEARING);
+ } else {
+ assertThat(transitions.mLastTransit).flags().doesNotContain(TRANSIT_FLAG_AOD_APPEARING);
+ }
transitions.flush();
// Start unlocking from AOD.
@@ -2705,14 +2717,7 @@ public class DisplayContentTests extends WindowTestsBase {
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
-
- if (Flags.aodTransition()) {
- assertThat(transitions.mLastTransit).flags()
- .containsExactly(TRANSIT_FLAG_AOD_APPEARING);
- } else {
- assertThat(transitions.mLastTransit).isNull();
- }
- transitions.flush();
+ assertThat(transitions.mLastTransit).isNull();
// Same API call a second time cancels the unlock, because AOD isn't changing.
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
@@ -2921,7 +2926,64 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
}
- @EnableFlags(FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_shouldShowSystemDecorationsDisplay() {
+ // Set up a non-default display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_notAllowContentModeSwitchDisplay() {
+ // Set up a non-default display without FLAG_ALLOWS_CONTENT_MODE_SWITCH enabled
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = FLAG_TRUSTED;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_untrustedDisplay() {
+ // Set up a non-default display without FLAG_TRUSTED enabled
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() {
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = (FLAG_ALLOWS_CONTENT_MODE_SWITCH | FLAG_TRUSTED);
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ spyOn(dc.mDisplay);
+ doReturn(false).when(dc.mDisplay).canHostTasks();
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+
+ doReturn(true).when(dc.mDisplay).canHostTasks();
+ dc.onDisplayInfoChangeApplied();
+ assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testForcedDensityRatioSetForExternalDisplays_persistDensityScaleFlagEnabled() {
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
@@ -2951,7 +3013,7 @@ public class DisplayContentTests extends WindowTestsBase {
displayContent.mExternalDisplayForcedDensityRatio, 0.01);
}
- @EnableFlags(FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testForcedDensityUpdateForExternalDisplays_persistDensityScaleFlagEnabled() {
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
@@ -2989,23 +3051,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(320, displayContent.mBaseDisplayDensity);
}
- @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
- @Test
- public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() {
- final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
- displayInfo.displayId = DEFAULT_DISPLAY + 1;
- final DisplayContent dc = createNewDisplay(displayInfo);
-
- spyOn(dc.mDisplay);
- doReturn(false).when(dc.mDisplay).canHostTasks();
- dc.onDisplayInfoChangeApplied();
- assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
-
- doReturn(true).when(dc.mDisplay).canHostTasks();
- dc.onDisplayInfoChangeApplied();
- assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
- }
-
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 4854f0d948b4..862158e8a0a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -362,7 +362,19 @@ public class DisplayPolicyTests extends WindowTestsBase {
@Test
public void testSwitchDecorInsets() {
- createNavBarWithProvidedInsets(mDisplayContent);
+ final WindowState win = createApplicationWindow();
+ final WindowState bar = createNavBarWithProvidedInsets(mDisplayContent);
+ bar.getFrame().set(0, mDisplayContent.mDisplayFrames.mHeight - NAV_BAR_HEIGHT,
+ mDisplayContent.mDisplayFrames.mWidth, mDisplayContent.mDisplayFrames.mHeight);
+ final int insetsId = bar.mAttrs.providedInsets[0].getId();
+ final InsetsSourceProvider provider = mDisplayContent.getInsetsStateController()
+ .getOrCreateSourceProvider(insetsId, bar.mAttrs.providedInsets[0].getType());
+ provider.setServerVisible(true);
+ provider.updateSourceFrame(bar.getFrame());
+
+ final InsetsState prevInsetsState = new InsetsState();
+ prevInsetsState.addSource(new InsetsSource(provider.getSource()));
+
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
final DisplayInfo info = mDisplayContent.getDisplayInfo();
final int w = info.logicalWidth;
@@ -385,6 +397,18 @@ public class DisplayPolicyTests extends WindowTestsBase {
// The current insets are restored from cache directly.
assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
info.logicalWidth, info.logicalHeight).mConfigFrame);
+ // Assume that the InsetsSource in current InsetsState is not updated yet. And it will be
+ // replaced by the one in cache.
+ InsetsState currentInsetsState = new InsetsState();
+ final InsetsSource prevSource = new InsetsSource(provider.getSource());
+ prevSource.getFrame().scale(0.5f);
+ currentInsetsState.addSource(prevSource);
+ currentInsetsState = mDisplayContent.getInsetsPolicy().adjustInsetsForWindow(
+ win, currentInsetsState);
+ if (com.android.window.flags.Flags.useCachedInsetsForDisplaySwitch()) {
+ assertEquals(prevInsetsState.peekSource(insetsId),
+ currentInsetsState.peekSource(insetsId));
+ }
// If screen is not fully turned on, then the cache should be preserved.
displayPolicy.screenTurnedOff(false /* acquireSleepToken */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index a57577a96f79..521d8364a31f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -37,7 +37,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
@@ -274,7 +274,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mSecondaryDisplay.mBaseDisplayDensity);
}
- @EnableFlags(Flags.FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testSetForcedDensityRatio() {
mDisplayWindowSettings.setForcedDensity(mSecondaryDisplay.getDisplayInfo(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index 7d59f4872d37..eb6d5cf8bb14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -21,10 +21,18 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.graphics.PixelFormat;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.view.WindowInsets;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
@@ -211,4 +219,29 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
mImeProvider.setFrozen(false);
assertFalse(mImeProvider.getSource().isVisible());
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+ public void testUpdateControlForTarget_remoteInsetsControlTarget() throws RemoteException {
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
+ makeWindowVisibleAndDrawn(ime);
+ mImeProvider.setWindowContainer(ime, null, null);
+ mImeProvider.setServerVisible(true);
+ mImeProvider.setClientVisible(true);
+ final WindowState inputTarget = newWindowBuilder("app", TYPE_APPLICATION).build();
+ final var displayWindowInsetsController = spy(createDisplayWindowInsetsController());
+ mDisplayContent.setRemoteInsetsController(displayWindowInsetsController);
+ final var controlTarget = mDisplayContent.mRemoteInsetsControlTarget;
+
+ inputTarget.setRequestedVisibleTypes(
+ WindowInsets.Type.defaultVisible() | WindowInsets.Type.ime());
+ mDisplayContent.setImeInputTarget(inputTarget);
+ mDisplayContent.setImeControlTarget(controlTarget);
+
+ assertTrue(inputTarget.isRequestedVisible(WindowInsets.Type.ime()));
+ assertFalse(controlTarget.isRequestedVisible(WindowInsets.Type.ime()));
+ mImeProvider.updateControlForTarget(controlTarget, true /* force */, null /* statsToken */);
+ verify(displayWindowInsetsController, times(1)).setImeInputTargetRequestedVisibility(
+ eq(true), any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 71e34ef220d3..3c6a89842af9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -93,7 +93,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
final Task task1 = createTask(mDisplayContent);
final Task task2 = createTask(mDisplayContent);
- task1.setAdjacentTaskFragment(task2);
+ task1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(task1, task2));
final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app");
final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index 67a95de8a5c1..ae005f228969 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -44,6 +44,7 @@ import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.pm.ActivityInfo.WindowLayout;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -52,6 +53,7 @@ import androidx.test.filters.MediumTest;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -115,6 +117,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
expected.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
expected.mWindowingMode = WINDOWING_MODE_PINNED;
expected.mBounds.set(200, 300, 400, 500);
+ expected.mNeedsSafeRegionBounds = true;
mPersister.putLaunchParams(userId, name, expected);
@@ -187,6 +190,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
params.mWindowingMode = WINDOWING_MODE_FREEFORM;
params.mBounds.set(0, 0, 30, 20);
params.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
+ params.mNeedsSafeRegionBounds = true;
final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
params);
@@ -227,6 +231,158 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
}
/**
+ * Tests only needs safe region bounds are not propagated if results are skipped.
+ */
+ @Test
+ public void testSkip_needsSafeRegionBoundsNotModified() {
+ final LaunchParams params1 = new LaunchParams();
+ params1.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ params2.mNeedsSafeRegionBounds = false;
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ assertEquals(result, positioner2.getLaunchParams());
+ }
+
+ /**
+ * Tests only needs safe region bounds are propagated even if results are continued.
+ */
+ @Test
+ public void testContinue_needsSafeRegionBoundsCarriedOver() {
+ final LaunchParams params1 = new LaunchParams();
+ final InstrumentedPositioner positioner1 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ params2.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ // Safe region is propagated from positioner1
+ assertEquals(result.mNeedsSafeRegionBounds,
+ positioner2.getLaunchParams().mNeedsSafeRegionBounds);
+ assertEquals(result.mWindowingMode, positioner1.getLaunchParams().mWindowingMode);
+ assertEquals(result.mBounds, positioner1.getLaunchParams().mBounds);
+ assertEquals(result.mPreferredTaskDisplayArea,
+ positioner1.getLaunchParams().mPreferredTaskDisplayArea);
+ }
+
+ /**
+ * Tests needs safe region bounds are modified if results from the next continue have been set.
+ */
+ @Test
+ public void testContinue_needsSafeRegionBoundsModifiedFromLaterContinue() {
+ final LaunchParams params1 = new LaunchParams();
+ params1.mNeedsSafeRegionBounds = false;
+ final InstrumentedPositioner positioner1 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ params2.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ // Safe region is propagated from positioner1
+ assertEquals(result.mNeedsSafeRegionBounds,
+ positioner1.getLaunchParams().mNeedsSafeRegionBounds);
+ assertEquals(result.mWindowingMode, positioner2.getLaunchParams().mWindowingMode);
+ assertEquals(result.mBounds, positioner2.getLaunchParams().mBounds);
+ assertEquals(result.mPreferredTaskDisplayArea,
+ positioner2.getLaunchParams().mPreferredTaskDisplayArea);
+ }
+
+ /**
+ * Tests only needs safe region bounds are propagated to result done even if there are skipped
+ * and continued results and continue sets true for needs safe region bounds.
+ */
+ @Test
+ public void testDone_ContinueSetsNeedsSafeRegionBounds() {
+ final LaunchParams params1 = new LaunchParams();
+ final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_DONE, params1);
+
+ final LaunchParams params2 = new LaunchParams();
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params2);
+
+ final LaunchParams params3 = new LaunchParams();
+ final InstrumentedPositioner positioner3 = new InstrumentedPositioner(RESULT_SKIP, params3);
+
+ final LaunchParams params4 = new LaunchParams();
+ params4.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner4 =
+ new InstrumentedPositioner(RESULT_CONTINUE, params4);
+
+ final LaunchParams params5 = new LaunchParams();
+ params5.mNeedsSafeRegionBounds = false;
+ final InstrumentedPositioner positioner5 = new InstrumentedPositioner(RESULT_SKIP, params5);
+
+ mController.registerModifier(positioner1);
+ mController.registerModifier(positioner2);
+ mController.registerModifier(positioner3);
+ mController.registerModifier(positioner4);
+ mController.registerModifier(positioner5);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ // Safe region is propagated from positioner4
+ assertEquals(result.mNeedsSafeRegionBounds,
+ positioner4.getLaunchParams().mNeedsSafeRegionBounds);
+ assertEquals(result.mWindowingMode, positioner1.getLaunchParams().mWindowingMode);
+ assertEquals(result.mBounds, positioner1.getLaunchParams().mBounds);
+ assertEquals(result.mPreferredTaskDisplayArea,
+ positioner1.getLaunchParams().mPreferredTaskDisplayArea);
+ }
+
+ /**
+ * Tests only needs safe region bounds are set if results are done.
+ */
+ @Test
+ public void testDone_needsSafeRegionBoundsModified() {
+ final LaunchParams params = new LaunchParams();
+ params.mNeedsSafeRegionBounds = true;
+ final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
+
+ mController.registerModifier(positioner);
+
+ final LaunchParams result = new LaunchParams();
+
+ mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
+ null /*options*/, null /*request*/, PHASE_BOUNDS, result);
+
+ assertEquals(result, positioner.getLaunchParams());
+ }
+
+ /**
* Tests preferred display id calculation for VR.
*/
@Test
@@ -372,6 +528,34 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
assertEquals(expected, task.mLastNonFullscreenBounds);
}
+ /**
+ * Ensures that app bounds are set to exclude freeform caption if window is in freeform.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
+ public void testLayoutTaskBoundsFreeformAppBounds() {
+ final Rect expected = new Rect(10, 20, 30, 40);
+
+ final LaunchParams params = new LaunchParams();
+ params.mBounds.set(expected);
+ params.mAppBounds.set(expected);
+ final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
+ final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ final ActivityOptions options = ActivityOptions.makeBasic().setFlexibleLaunchSize(true);
+
+ mController.registerModifier(positioner);
+
+ assertNotEquals(expected, task.getBounds());
+
+ layoutTask(task, options);
+
+ // Task will make adjustments to requested bounds. We only need to guarantee that the
+ // requested bounds are expected.
+ assertEquals(expected,
+ task.getRequestedOverrideConfiguration().windowConfiguration.getAppBounds());
+ }
+
public static class InstrumentedPositioner implements LaunchParamsModifier {
private final int mReturnVal;
@@ -473,4 +657,9 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
mController.layoutTask(task, null /* layout */, null /* activity */, null /* source */,
null /* options */);
}
+
+ private void layoutTask(@NonNull Task task, ActivityOptions options) {
+ mController.layoutTask(task, null /* layout */, null /* activity */, null /* source */,
+ options /* options */);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 66d7963946b9..1356718be4dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -30,7 +30,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
index 51e02405e489..8ee5999e7c24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
@@ -38,7 +38,6 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.InputWindowHandle;
-import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.server.testutils.StubTransaction;
@@ -76,8 +75,7 @@ public class LetterboxAttachInputTest extends WindowTestsBase {
doReturn(0.5f).when(letterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
mWindowState = createWindowState();
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
- mock(AppCompatReachabilityPolicy.class), letterboxOverrides,
- () -> mock(SurfaceControl.class));
+ mock(AppCompatReachabilityPolicy.class), letterboxOverrides);
mTransaction = spy(StubTransaction.class);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index a51a44f6167b..e5533b659e03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -68,7 +68,6 @@ public class LetterboxTest {
private SurfaceControlMocker mSurfaces;
private SurfaceControl.Transaction mTransaction;
- private SurfaceControl mParentSurface = mock(SurfaceControl.class);
private AppCompatLetterboxOverrides mLetterboxOverrides;
private WindowState mWindowState;
@@ -84,7 +83,7 @@ public class LetterboxTest {
doReturn(0.5f).when(mLetterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
mWindowState = mock(WindowState.class);
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
- mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides, () -> mParentSurface);
+ mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides);
mTransaction = spy(StubTransaction.class);
}
@@ -259,22 +258,6 @@ public class LetterboxTest {
}
@Test
- public void testNeedsApplySurfaceChanges_setParentSurface() {
- mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
- applySurfaceChanges();
-
- verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
- assertFalse(mLetterbox.needsApplySurfaceChanges());
-
- mParentSurface = mock(SurfaceControl.class);
-
- assertTrue(mLetterbox.needsApplySurfaceChanges());
-
- applySurfaceChanges();
- verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
- }
-
- @Test
public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
applySurfaceChanges();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 8a7e7434e604..2c6884e7a35a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -52,7 +52,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static java.lang.Integer.MAX_VALUE;
@@ -1293,7 +1293,7 @@ public class RecentTasksTest extends WindowTestsBase {
// Add secondTask to top again
mRecentTasks.add(secondTask);
- verifyZeroInteractions(controller);
+ verifyNoMoreInteractions(controller);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index e4a1bf603cf0..34e9bed8463a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -31,6 +31,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
import static com.android.server.wm.ActivityRecord.State.FINISHING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -82,6 +83,7 @@ import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
+import android.view.DisplayInfo;
import androidx.test.filters.MediumTest;
@@ -1379,6 +1381,23 @@ public class RootWindowContainerTests extends WindowTestsBase {
verify(controller, never()).notifyTaskProfileLocked(any(), anyInt());
}
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testOnDisplayBelongToTopologyChanged() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ final int displayId = dc.getDisplayId();
+
+ doReturn(dc).when(mRootWindowContainer).getDisplayContentOrCreate(displayId);
+ doReturn(true).when(mWm.mDisplayWindowSettings).shouldShowSystemDecorsLocked(dc);
+
+ mRootWindowContainer.onDisplayAdded(displayId);
+ verify(mWm.mDisplayManagerInternal, times(1)).onDisplayBelongToTopologyChanged(anyInt(),
+ anyBoolean());
+ }
+
/**
* Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
* info for test cases.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 4e09d6afe74b..fc70b80790c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -67,6 +67,7 @@ import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MU
import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.window.flags.Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -109,6 +110,7 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.InputDevice;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -561,6 +563,42 @@ public class SizeCompatTests extends WindowTestsBase {
assertDownScaled();
}
+ @EnableFlags(FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testFixedMiscConfigurationWhenMovingToDisplay() {
+ setUpDisplaySizeWithApp(1000, 2500);
+
+ final DisplayContent newDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
+ final InputDevice device = new InputDevice.Builder()
+ .setAssociatedDisplayId(newDisplay.mDisplayId)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL)
+ .build();
+ final InputDevice[] devices = {device};
+ doReturn(true).when(newDisplay.mWmService.mInputManager)
+ .canDispatchToDisplay(device.getId(), newDisplay.mDisplayId);
+ doReturn(devices).when(newDisplay.mWmService.mInputManager).getInputDevices();
+ mTask.mWmService.mIsTouchDevice = true;
+ mTask.mWmService.displayReady();
+
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ final Configuration originalConfiguration = mActivity.getConfiguration();
+ final int originalTouchscreen = originalConfiguration.touchscreen;
+ final int originalNavigation = originalConfiguration.navigation;
+ final int originalKeyboard = originalConfiguration.keyboard;
+
+ // Move the non-resizable activity to the new display.
+ mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+
+ final Configuration newConfiguration = mActivity.getConfiguration();
+ assertEquals(originalTouchscreen, newConfiguration.touchscreen);
+ assertEquals(originalNavigation, newConfiguration.navigation);
+ assertEquals(originalKeyboard, newConfiguration.keyboard);
+ // TODO(b/399749909): assert keyboardHidden, hardkeyboardHidden, and navigationHidden too.
+ }
+
@Test
public void testFixedScreenBoundsWhenDisplaySizeChanged() {
setUpDisplaySizeWithApp(1000, 2500);
@@ -4700,6 +4738,213 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_noManifestProperty_returnsTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ // For an activity letterboxed only due to safe region, areBoundsLetterboxed will return
+ // false
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+ // Since no manifest property is defined, the activity is opted in by default
+ assertTrue(mActivity.mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForActivity_returnsTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ // Activity can opt-out the safe region letterboxing by component level property.
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since activity manifest property is defined as true, the activity can be letterboxed
+ // for safe region
+ assertTrue(optOutActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForActivity_returnsFalse() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since activity manifest property is defined as false, the activity can not be letterboxed
+ // for safe region
+ assertFalse(optOutActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForApplication_returnsFalse() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since application manifest property is defined as false, the activity can not be
+ // letterboxed for safe region
+ assertFalse(optOutAppActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForApplication_returnsTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);
+
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
+ updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);
+ final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);
+
+ // Since application manifest property is defined as true, the activity can be letterboxed
+ // for safe region
+ assertTrue(optOutAppActivity.mAppCompatController
+ .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
+ }
+
+ private void updateApplicationLevelAllowSafeRegionLetterboxingProperty(ComponentName name,
+ PackageManager pm, boolean propertyValueForApplication) {
+ final PackageManager.Property propertyForApplication = new PackageManager.Property(
+ "propertyName", /* value */ propertyValueForApplication, name.getPackageName(),
+ name.getClassName());
+ try {
+ doReturn(propertyForApplication).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ name.getPackageName(), /* className */ null, /* userId */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void updateActivityLevelAllowSafeRegionLetterboxingProperty(ComponentName name,
+ PackageManager pm, boolean propertyValueForActivity) {
+ final PackageManager.Property propertyForActivity = new PackageManager.Property(
+ "propertyName", /* value */ propertyValueForActivity, name.getPackageName(),
+ name.getClassName());
+ try {
+ doReturn(propertyForActivity).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
+ name.getPackageName(), name.getClassName(), /* userId */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndFixedOrientation_returnTrue() {
+ setUpLandscapeLargeScreenDisplayWithApp();
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 500, /* dh */ 200);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is letterboxed due to fixed orientation within the safe region
+ assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION);
+ assertFalse(mActivity.mAppCompatController.getSafeRegionPolicy()
+ .isLetterboxedForSafeRegionOnlyAllowed());
+ assertTrue(safeRegionBounds.contains(mActivity.getBounds()));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndAspectRatio_returnTrue() {
+ setUpPortraitLargeScreenDisplayWithApp();
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
+
+ final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 200, /* dh */ 300);
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is letterboxed due to min aspect ratio within the safe region
+ assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForAspectRatioOnly());
+ assertFalse(mActivity.inSizeCompatMode());
+ assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
+ assertTrue(safeRegionBounds.contains(mActivity.getBounds()));
+ }
+
+ private Rect setupSafeRegionBoundsParameters(int dw, int dh) {
+ final AppCompatController appCompatController = mActivity.mAppCompatController;
+ final AppCompatSafeRegionPolicy safeRegionPolicy =
+ appCompatController.getSafeRegionPolicy();
+ safeRegionPolicy.setNeedsSafeRegionBounds(true);
+ spyOn(mTask);
+ final Rect safeRegionBounds = new Rect(100, 200, 100 + dw, 200 + dh);
+ doReturn(safeRegionBounds).when(mTask).getSafeRegionBounds();
+ return safeRegionBounds;
+ }
+
+ @Test
public void testAreBoundsLetterboxed_letterboxedForSizeCompat_returnsTrue() {
setUpDisplaySizeWithApp(1000, 2500);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 3776b03695d5..b558fad84efa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -78,6 +78,7 @@ import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.protolog.PerfettoProtoLogImpl;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.WmProtoLogGroups;
import com.android.server.AnimationThread;
@@ -187,7 +188,10 @@ public class SystemServicesTestRule implements TestRule {
}
private void setUp() {
- ProtoLog.init(WmProtoLogGroups.values());
+ if (ProtoLog.getSingleInstance() == null) {
+ ProtoLog.init(WmProtoLogGroups.values());
+ PerfettoProtoLogImpl.waitForInitialization();
+ }
if (mOnBeforeServicesCreated != null) {
mOnBeforeServicesCreated.run();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 986532ce5897..ec83c50e95aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -87,7 +87,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -113,7 +114,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
final Task adjacentRootTask = createTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
taskDisplayArea.setLaunchRootTask(rootTask,
new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -135,7 +137,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
adjacentRootTask.mCreatedByOrganizer = true;
createActivityRecord(adjacentRootTask);
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -821,7 +824,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
adjacentRootTask.mCreatedByOrganizer = true;
final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/);
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask);
+ adjacentRootTask.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
// Verify the launch root with candidate task
Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index ab76ae8e378a..76660bdc7355 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -784,7 +784,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.setFragmentToken(fragmentToken2)
.build();
mWindowOrganizerController.mLaunchTaskFragments.put(fragmentToken2, taskFragment2);
- mTaskFragment.setAdjacentTaskFragment(taskFragment2);
+ mTaskFragment.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(mTaskFragment, taskFragment2));
mTransaction.clearAdjacentTaskFragments(mFragmentToken);
mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
@@ -1267,7 +1268,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
- public void testTaskFragmentInPip_setAdjacentTaskFragment() {
+ public void testTaskFragmentInPip_setAdjacentTaskFragments() {
setupTaskFragmentInPip();
spyOn(mWindowOrganizerController);
@@ -1279,7 +1280,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment), eq(OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS),
any(IllegalArgumentException.class));
- verify(mTaskFragment, never()).setAdjacentTaskFragment(any());
+ verify(mTaskFragment, never()).setAdjacentTaskFragments(any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index cc2a76dcc9f2..7c1d7fec819b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -67,7 +67,6 @@ import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Binder;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import android.view.View;
@@ -363,7 +362,7 @@ public class TaskFragmentTest extends WindowTestsBase {
doReturn(true).when(primaryActivity).supportsPictureInPicture();
doReturn(false).when(secondaryActivity).supportsPictureInPicture();
- primaryTf.setAdjacentTaskFragment(secondaryTf);
+ primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf));
primaryActivity.setState(RESUMED, "test");
secondaryActivity.setState(RESUMED, "test");
@@ -390,7 +389,8 @@ public class TaskFragmentTest extends WindowTestsBase {
task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskFragment0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
taskFragment0.setBounds(taskFragmentBounds);
- taskFragment0.setAdjacentTaskFragment(taskFragment1);
+ taskFragment0.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(taskFragment0, taskFragment1));
taskFragment0.setCompanionTaskFragment(taskFragment1);
taskFragment0.setAnimationParams(new TaskFragmentAnimationParams.Builder()
.setAnimationBackgroundColor(Color.GREEN)
@@ -779,7 +779,7 @@ public class TaskFragmentTest extends WindowTestsBase {
.setOrganizer(mOrganizer)
.setFragmentToken(new Binder())
.build();
- tf0.setAdjacentTaskFragment(tf1);
+ tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1));
tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
task.setBounds(0, 0, 1200, 1000);
@@ -834,7 +834,7 @@ public class TaskFragmentTest extends WindowTestsBase {
final Task task = createTask(mDisplayContent);
final TaskFragment tf0 = createTaskFragmentWithActivity(task);
final TaskFragment tf1 = createTaskFragmentWithActivity(task);
- tf0.setAdjacentTaskFragment(tf1);
+ tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1));
tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
task.setBounds(0, 0, 1200, 1000);
@@ -982,7 +982,8 @@ public class TaskFragmentTest extends WindowTestsBase {
.setOrganizer(mOrganizer)
.setFragmentToken(new Binder())
.build();
- taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+ taskFragmentLeft.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight));
taskFragmentLeft.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
taskFragmentRight.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
task.setBounds(0, 0, 1200, 1000);
@@ -1051,8 +1052,8 @@ public class TaskFragmentTest extends WindowTestsBase {
.setParentTask(task)
.createActivityCount(1)
.build();
- taskFragmentRight.setAdjacentTaskFragment(taskFragmentLeft);
- taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+ taskFragmentRight.setAdjacentTaskFragments(
+ new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight));
final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity();
final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity();
@@ -1103,7 +1104,6 @@ public class TaskFragmentTest extends WindowTestsBase {
Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp));
}
- @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testAdjacentSetForTaskFragments() {
final Task task = createTask(mDisplayContent);
@@ -1119,7 +1119,6 @@ public class TaskFragmentTest extends WindowTestsBase {
() -> new TaskFragment.AdjacentSet(tf0, tf1, tf2));
}
- @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testSetAdjacentTaskFragments() {
final Task task0 = createTask(mDisplayContent);
@@ -1148,7 +1147,6 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(task2.hasAdjacentTaskFragment());
}
- @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testClearAdjacentTaskFragments() {
final Task task0 = createTask(mDisplayContent);
@@ -1167,7 +1165,6 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(task2.hasAdjacentTaskFragment());
}
- @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testRemoveFromAdjacentTaskFragments() {
final Task task0 = createTask(mDisplayContent);
@@ -1190,7 +1187,6 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(task1.isAdjacentTo(task1));
}
- @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testRemoveFromAdjacentTaskFragmentsWhenRemove() {
final Task task0 = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
index 51ea498811fc..f22ecb5eb3f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
@@ -68,11 +68,8 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas
public void testPersistAndLoadSnapshot() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
- final File[] files = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
- final File[] nonExistsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
+ final File[] files = convertFilePath("1.proto", "1.jpg");
+ final File[] nonExistsFiles = convertFilePath("1_reduced.proto");
assertTrueForFiles(files, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */);
@@ -92,14 +89,9 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas
taskIds.add(1);
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
mSnapshotPersistQueue.waitForQueueEmpty();
- final File[] existsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
- final File[] nonExistsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/2.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")};
+ final File[] existsFiles = convertFilePath("1.proto", "1.jpg");
+ final File[] nonExistsFiles = convertFilePath("1_reduced.proto", "2.proto", "2.jpg",
+ "2_reduced.jpg");
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
@@ -112,14 +104,8 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
- final File[] existsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/2.jpg")};
- final File[] nonExistsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")};
+ final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "2.proto", "2.jpg");
+ final File[] nonExistsFiles = convertFilePath("1_reduced.jpg", "2_reduced.jpg");
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 4b54e4464ca7..af06c14516a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -75,9 +75,7 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
public void testPersistAndLoadSnapshot() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
- final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
+ final File[] files = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg");
assertTrueForFiles(files, File::exists, " must exist");
final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */);
assertNotNull(snapshot);
@@ -140,13 +138,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mSnapshotPersistQueue.waitForQueueEmpty();
// Make sure 1,2 were purged but removeObsoleteFiles wasn't.
- final File[] existsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/3.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/4.proto")};
- final File[] nonExistsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/100.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.proto")};
+ final File[] existsFiles = convertFilePath("3.proto", "4.proto");
+ final File[] nonExistsFiles = convertFilePath("100.proto", "1.proto", "2.proto");
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
@@ -427,14 +420,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
taskIds.add(1);
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
mSnapshotPersistQueue.waitForQueueEmpty();
- final File[] existsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
- final File[] nonExistsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/2.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/2.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")};
+ final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg");
+ final File[] nonExistsFiles = convertFilePath("2.proto", "2.jpg", "2_reduced.jpg");
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
@@ -447,13 +434,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
- final File[] existsFiles = new File[]{
- new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2.proto"),
- new File(FILES_DIR.getPath() + "/snapshots/2.jpg"),
- new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")};
+ final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg", "2.proto",
+ "2.jpg", "2_reduced.jpg");
assertTrueForFiles(existsFiles, File::exists, " must exist");
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 1e16c97de647..547fc04f9fac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.ContextWrapper;
import android.content.res.Resources;
@@ -46,6 +47,7 @@ import android.window.TaskSnapshot;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.AfterClass;
@@ -129,12 +131,33 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
return;
}
for (File file : files) {
- if (!file.isDirectory()) {
- file.delete();
+ if (file.isDirectory()) {
+ final File[] subFiles = file.listFiles();
+ if (subFiles == null) {
+ continue;
+ }
+ for (File subFile : subFiles) {
+ subFile.delete();
+ }
}
+ file.delete();
}
}
+ File[] convertFilePath(@NonNull String... fileNames) {
+ final File[] files = new File[fileNames.length];
+ final String path;
+ if (Flags.scrambleSnapshotFileName()) {
+ path = mPersister.mPersistInfoProvider.getDirectory(mTestUserId).getPath();
+ } else {
+ path = FILES_DIR.getPath() + "/snapshots/";
+ }
+ for (int i = 0; i < fileNames.length; i++) {
+ files[i] = new File(path, fileNames[i]);
+ }
+ return files;
+ }
+
TaskSnapshot createSnapshot() {
return new TaskSnapshotBuilder().setTopActivityComponent(getUniqueComponentName()).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 724d7e7c111c..044aacc4b988 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1750,8 +1750,7 @@ public class TaskTests extends WindowTestsBase {
primary.mVisibleRequested = true;
secondary.mVisibleRequested = true;
- primary.setAdjacentTaskFragment(secondary);
- secondary.setAdjacentTaskFragment(primary);
+ primary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primary, secondary));
primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
doReturn(true).when(primary).shouldBoostDimmer();
task.assignChildLayers(t);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 4f310de1e48b..e0e65e67762e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1431,6 +1431,93 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBounds_appliedOnNodeAndChildren() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertNull(root.getSafeRegionBounds());
+ assertNull(child1.getSafeRegionBounds());
+ assertNull(child11.getSafeRegionBounds());
+ assertNull(child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ final Rect tempSafeRegionBounds1 = new Rect(50, 50, 200, 300);
+ child1.setSafeRegionBounds(tempSafeRegionBounds1);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ // Set different safe region bounds on child11
+ final Rect tempSafeRegionBounds2 = new Rect(30, 30, 200, 200);
+ child11.setSafeRegionBounds(tempSafeRegionBounds2);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds2, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBounds_resetSafeRegionBounds() {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+
+ assertNull(root.getSafeRegionBounds());
+ assertNull(child1.getSafeRegionBounds());
+ assertNull(child11.getSafeRegionBounds());
+ assertNull(child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ final Rect tempSafeRegionBounds1 = new Rect(50, 50, 200, 300);
+ child1.setSafeRegionBounds(tempSafeRegionBounds1);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+
+ // Set different safe region bounds on child11
+ final Rect tempSafeRegionBounds2 = new Rect(30, 30, 200, 200);
+ child11.setSafeRegionBounds(tempSafeRegionBounds2);
+
+ assertEquals(tempSafeRegionBounds2, child11.getSafeRegionBounds());
+
+ // Reset safe region bounds on child11. Now child11 will use child1 safe region bounds.
+ child11.setSafeRegionBounds(/* safeRegionBounds */null);
+
+ assertNull(root.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child1.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child11.getSafeRegionBounds());
+ assertEquals(tempSafeRegionBounds1, child12.getSafeRegionBounds());
+ assertNull(child2.getSafeRegionBounds());
+ assertNull(child21.getSafeRegionBounds());
+ }
+
+ @Test
public void testSetExcludeInsetsTypes_appliedOnNodeAndChildren() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index dcb68620e361..c0be214241a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -36,8 +36,10 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.times;
import android.content.Intent;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -46,6 +48,8 @@ import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,6 +66,8 @@ import java.util.List;
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowContainerTransactionTests extends WindowTestsBase {
+ private final Rect mSafeRegionBounds = new Rect(50, 50, 200, 300);
+
@Test
public void testRemoveTask() {
final Task rootTask = createTask(mDisplayContent);
@@ -209,6 +215,152 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnTaskDisplayArea() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = taskDisplayArea.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the task display area
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(taskDisplayArea.getSafeRegionBounds(), mSafeRegionBounds);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the root task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = task.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnTask_resetSafeRegionBounds() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = task.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+
+ // Reset safe region bounds on the task
+ wct.setSafeRegionBounds(token, /* safeRegionBounds */null);
+ applyTransaction(wct);
+
+ assertNull(activity.getSafeRegionBounds());
+ assertNull(task.getSafeRegionBounds());
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTaskAndTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the root task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ // Set different safe region bounds on task
+ final Rect tempSafeRegionBounds = new Rect(30, 30, 200, 200);
+ wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), tempSafeRegionBounds);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), tempSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), tempSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTaskAndTask_resetSafeRegionBoundsOnTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerToken token = rootTask.mRemoteToken.toWindowContainerToken();
+ // Set safe region bounds on the root task
+ wct.setSafeRegionBounds(token, mSafeRegionBounds);
+ // Set different safe region bounds on task
+ final Rect mTmpSafeRegionBounds = new Rect(30, 30, 200, 200);
+ wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(), mTmpSafeRegionBounds);
+ applyTransaction(wct);
+
+ // Task and activity will use different safe region bounds
+ assertEquals(activity.getSafeRegionBounds(), mTmpSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mTmpSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+
+ // Reset safe region bounds on task
+ wct.setSafeRegionBounds(task.mRemoteToken.toWindowContainerToken(),
+ /* safeRegionBounds */null);
+ applyTransaction(wct);
+
+ assertEquals(activity.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(task.getSafeRegionBounds(), mSafeRegionBounds);
+ assertEquals(rootTask.getSafeRegionBounds(), mSafeRegionBounds);
+ assertNull(taskDisplayArea.getSafeRegionBounds());
+ }
+
+ @Test
public void testDesktopMode_moveTaskToFront() {
final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 8606581539ff..669f5d28267d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -24,7 +24,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import android.platform.test.annotations.Presubmit;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7030d986494f..6a35fa8e20ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -61,6 +61,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -910,7 +911,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
final RunningTaskInfo info2 = task2.getTaskInfo();
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setAdjacentRoots(info1.token, info2.token);
+ wct.setAdjacentRootSet(info1.token, info2.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertTrue(task1.isAdjacentTo(task2));
assertTrue(task2.isAdjacentTo(task1));
@@ -929,7 +930,6 @@ public class WindowOrganizerTests extends WindowTestsBase {
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
}
- @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testSetAdjacentLaunchRootSet() {
final DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
@@ -1569,6 +1569,51 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTask() {
+ Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, null);
+ final Task task1 = createRootTask();
+ final Task task2 = createTask(rootTask, false /* fakeDraw */);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ Rect safeRegionBounds = new Rect(50, 50, 200, 300);
+
+ wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), safeRegionBounds);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertEquals(rootTask.getSafeRegionBounds(), safeRegionBounds);
+ assertEquals(task2.getSafeRegionBounds(), safeRegionBounds);
+ assertNull(task1.getSafeRegionBounds());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING)
+ public void testSetSafeRegionBoundsOnRootTask_resetSafeRegionBounds() {
+ Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, null);
+ final Task task1 = createRootTask();
+ final Task task2 = createTask(rootTask, false /* fakeDraw */);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ Rect safeRegionBounds = new Rect(50, 50, 200, 300);
+
+ wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(), safeRegionBounds);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertEquals(rootTask.getSafeRegionBounds(), safeRegionBounds);
+ assertEquals(task2.getSafeRegionBounds(), safeRegionBounds);
+ assertNull(task1.getSafeRegionBounds());
+
+ // Reset safe region bounds on the root task
+ wct.setSafeRegionBounds(rootTask.mRemoteToken.toWindowContainerToken(),
+ /* safeRegionBounds */null);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+ assertNull(rootTask.getSafeRegionBounds());
+ assertNull(task2.getSafeRegionBounds());
+ assertNull(task1.getSafeRegionBounds());
+ }
+
+ @Test
public void testReparentToOrganizedTask() {
final ITaskOrganizer organizer = registerMockOrganizer();
Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 59ee2f5c8e9f..5624677779a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -23,9 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsSource.ID_IME;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -88,7 +85,6 @@ import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -673,56 +669,6 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
- public void testSeamlesslyRotateWindow() {
- final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
- final SurfaceControl.Transaction t = spy(StubTransaction.class);
-
- makeWindowVisible(app);
- app.mSurfaceControl = mock(SurfaceControl.class);
- final Rect frame = app.getFrame();
- frame.set(10, 20, 60, 80);
- app.updateSurfacePosition(t);
- assertTrue(app.mLastSurfacePosition.equals(frame.left, frame.top));
- app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_90, true /* requested */);
- assertTrue(app.mSeamlesslyRotated);
-
- // Verify we un-rotate the window state surface.
- final Matrix matrix = new Matrix();
- // Un-rotate 90 deg.
- matrix.setRotate(270);
- // Translate it back to origin.
- matrix.postTranslate(0, mDisplayInfo.logicalWidth);
- verify(t).setMatrix(eq(app.mSurfaceControl), eq(matrix), any(float[].class));
-
- // Verify we update the position as well.
- final float[] curSurfacePos = {app.mLastSurfacePosition.x, app.mLastSurfacePosition.y};
- matrix.mapPoints(curSurfacePos);
- verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1]));
-
- app.finishSeamlessRotation(t);
- assertFalse(app.mSeamlesslyRotated);
- assertNull(app.mPendingSeamlessRotate);
-
- // Simulate the case with deferred layout and animation.
- app.resetSurfacePositionForAnimationLeash(t);
- clearInvocations(t);
- mWm.mWindowPlacerLocked.deferLayout();
- app.updateSurfacePosition(t);
- // Because layout is deferred, the position should keep the reset value.
- assertTrue(app.mLastSurfacePosition.equals(0, 0));
-
- app.seamlesslyRotateIfAllowed(t, ROTATION_0, ROTATION_270, true /* requested */);
- // The last position must be updated so the surface can be unrotated properly.
- assertTrue(app.mLastSurfacePosition.equals(frame.left, frame.top));
- matrix.setRotate(90);
- matrix.postTranslate(mDisplayInfo.logicalHeight, 0);
- curSurfacePos[0] = frame.left;
- curSurfacePos[1] = frame.top;
- matrix.mapPoints(curSurfacePos);
- verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1]));
- }
-
- @Test
public void testVisibilityChangeSwitchUser() {
final WindowState window = newWindowBuilder("app", TYPE_APPLICATION).build();
window.mHasSurface = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 57ab13ffee89..471b065aebb4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -122,7 +122,6 @@ import android.window.WindowContainerTransaction;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.After;
@@ -289,18 +288,6 @@ public class WindowTestsBase extends SystemServiceTestsBase {
mAtm.mWindowManager.mAppCompatConfiguration
.setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
- // Setup WallpaperController crop utils with a simple center-align strategy
- WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> {
- Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
- crop.scale(Math.min(
- ((float) bitmapSize.x) / displaySize.x,
- ((float) bitmapSize.y) / displaySize.y));
- crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2);
- return crop;
- };
- mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils);
- mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils);
-
checkDeviceSpecificOverridesNotApplied();
}
@@ -1890,7 +1877,7 @@ public class WindowTestsBase extends SystemServiceTestsBase {
mSecondary = mService.mTaskOrganizerController.createRootTask(
display, WINDOWING_MODE_MULTI_WINDOW, null);
- mPrimary.setAdjacentTaskFragment(mSecondary);
+ mPrimary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(mPrimary, mSecondary));
display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary);
final Rect primaryBounds = new Rect();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index a02c3db1e636..8907a72e0b34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -35,16 +37,19 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
+import android.graphics.Matrix;
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.WindowContext;
@@ -335,6 +340,31 @@ public class WindowTokenTests extends WindowTestsBase {
}
@Test
+ public void testSeamlesslyRotate() {
+ final SurfaceControl.Transaction t = mTransaction;
+ final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
+ token.mLastSurfacePosition.x = 10;
+ token.mLastSurfacePosition.y = 20;
+ final SeamlessRotator rotator = new SeamlessRotator(ROTATION_0, ROTATION_90,
+ mDisplayContent.getDisplayInfo(), false /* applyFixedTransformationHint */);
+ clearInvocations(t);
+ rotator.unrotate(t, token);
+
+ // Verify surface is un-rotated.
+ final Matrix matrix = new Matrix();
+ // Un-rotate 90 deg.
+ matrix.setRotate(270);
+ // Translate it back to origin.
+ matrix.postTranslate(0, mDisplayInfo.logicalWidth);
+ verify(t).setMatrix(eq(token.mSurfaceControl), eq(matrix), any(float[].class));
+
+ final float[] curSurfacePos = {token.mLastSurfacePosition.x, token.mLastSurfacePosition.y};
+ matrix.mapPoints(curSurfacePos);
+ verify(t).setPosition(eq(token.mSurfaceControl),
+ eq(curSurfacePos[0]), eq(curSurfacePos[1]));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
public void onDisplayChanged_differentDisplay_reparented() {
final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
index a1d35a7d447c..0749c0b94537 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
@@ -22,7 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -118,7 +118,7 @@ public class WindowTracingLegacyTest {
@Test
public void trace_discared_whenNotTracing() {
mWindowTracing.logState("where");
- verifyZeroInteractions(mWmMock);
+ verifyNoMoreInteractions(mWmMock);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
index 9367941e32a3..3da279bb9663 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
@@ -22,7 +22,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -128,7 +128,7 @@ public class WindowTracingPerfettoTest {
@Test
public void trace_ignoresLogStateCalls_ifTracingIsDisabled() {
sWindowTracing.logState("where");
- verifyZeroInteractions(sWmMock);
+ verifyNoMoreInteractions(sWmMock);
}
@Test
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
deleted file mode 100644
index 31c5986c45b8..000000000000
--- a/services/usb/java/com/android/server/usb/UsbManagerInternal.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.usb;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.hardware.usb.IUsbOperationInternal;
-import android.hardware.usb.UsbPort;
-import android.util.ArraySet;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * UsbManagerInternal provides internal APIs for the UsbService to
- * reduce IPC overhead costs and support internal USB data signal stakers.
- *
- * @hide Only for use within the system server.
- */
-public abstract class UsbManagerInternal {
-
- public static final int OS_USB_DISABLE_REASON_AAPM = 0;
- public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {OS_USB_DISABLE_REASON_AAPM,
- OS_USB_DISABLE_REASON_LOCKDOWN_MODE})
- public @interface OsUsbDisableReason {
- }
-
- public abstract boolean enableUsbData(String portId, boolean enable,
- int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
-
- public abstract UsbPort[] getPorts();
-
-} \ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 4395b76d91cc..7808b2e318b2 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -27,7 +27,10 @@ import static android.hardware.usb.UsbPortStatus.MODE_UFP;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import android.hardware.usb.IUsbManagerInternal;
+
import android.annotation.NonNull;
+import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
@@ -80,12 +83,16 @@ import dalvik.annotation.optimization.NeverCompile;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* UsbService manages all USB related state, including both host and device support.
* Host related events and calls are delegated to UsbHostManager, and device related
@@ -93,6 +100,15 @@ import java.util.concurrent.CompletableFuture;
*/
public class UsbService extends IUsbManager.Stub {
+ public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+ public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {OS_USB_DISABLE_REASON_AAPM,
+ OS_USB_DISABLE_REASON_LOCKDOWN_MODE})
+ public @interface OsUsbDisableReason {
+ }
+
public static class Lifecycle extends SystemService {
private UsbService mUsbService;
private final CompletableFuture<Void> mOnStartFinished = new CompletableFuture<>();
@@ -227,7 +243,7 @@ public class UsbService extends IUsbManager.Stub {
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
- LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ LocalServices.addService(IUsbManagerInternal.class, new UsbManagerInternalImpl());
}
}
@@ -246,7 +262,7 @@ public class UsbService extends IUsbManager.Stub {
mPermissionManager = new UsbPermissionManager(context, this);
if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
- LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ LocalServices.addService(IUsbManagerInternal.class, new UsbManagerInternalImpl());
}
}
@@ -1536,24 +1552,30 @@ public class UsbService extends IUsbManager.Stub {
enableUsbDataInternal(port.getId(), !lockDownTriggeredByUser,
STRONG_AUTH_OPERATION_ID,
new IUsbOperationInternal.Default(),
- UsbManagerInternal.OS_USB_DISABLE_REASON_LOCKDOWN_MODE,
+ OS_USB_DISABLE_REASON_LOCKDOWN_MODE,
true);
}
}
}
- private class UsbManagerInternalImpl extends UsbManagerInternal {
- @Override
- public boolean enableUsbData(String portId, boolean enable,
- int operationId, IUsbOperationInternal callback,
- @OsUsbDisableReason int disableReason) {
- return enableUsbDataInternal(portId, enable, operationId, callback,
- disableReason, true);
- }
+ private class UsbManagerInternalImpl extends IUsbManagerInternal.Stub {
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
@Override
- public UsbPort[] getPorts() {
- return mPortManager.getPorts();
+ public boolean enableUsbDataSignal(boolean enable,
+ @OsUsbDisableReason int disableReason) {
+ boolean result = true;
+ int operationId = sUsbOperationCount.incrementAndGet() + disableReason;
+ for (UsbPort port : mPortManager.getPorts()) {
+ boolean success = enableUsbDataInternal(port.getId(), enable, operationId,
+ new IUsbOperationInternal.Default(), disableReason, true);
+ if(!success) {
+ Slog.e(TAG, "enableUsbDataInternal failed to change USB port "
+ + port.getId() + "state to " + enable);
+ }
+ result &= success;
+ }
+ return result;
}
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 89b98509de34..a727df7a7efb 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -43,6 +43,8 @@ import android.media.AudioManagerInternal;
import android.media.permission.Identity;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
@@ -837,6 +839,13 @@ final class HotwordDetectionConnection {
private final int mBindingFlags;
private final int mInstanceNumber;
+ private static final HandlerThread mHandler;
+
+ static {
+ mHandler = new HandlerThread("Sandbox detection connector");
+ mHandler.start();
+ }
+
private boolean mRespectServiceConnectionStatusChanged = true;
private boolean mIsBound = false;
private boolean mIsLoggedFirstConnect = false;
@@ -883,6 +892,11 @@ final class HotwordDetectionConnection {
}
}
+ @Override // from ServiceConnector.Impl
+ protected Handler getJobHandler() {
+ return mHandler.getThreadHandler();
+ }
+
@Override
protected long getAutoDisconnectTimeoutMs() {
return -1;
@@ -1151,14 +1165,12 @@ final class HotwordDetectionConnection {
}
private void updateServiceIdentity(ServiceConnection connection) {
- connection.run(service -> service.ping(new IRemoteCallback.Stub() {
+ connection.run(service -> service.ping(new ISandboxedDetectionService.IPingMe.Stub() {
@Override
- public void sendResult(Bundle bundle) throws RemoteException {
+ public void onPing() throws RemoteException {
// TODO: Exit if the service has been unbound already (though there's a very low
// chance this happens).
- if (DEBUG) {
- Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
- }
+ Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
// TODO: Have the provider point to the current state stored in
// VoiceInteractionManagerServiceImpl.
final int uid = Binder.getCallingUid();
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 531f51604507..9e57fd3c1a7b 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -30,6 +30,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.flags.Flags;
@@ -680,6 +681,7 @@ public final class Call {
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
private final Uri mContactPhotoUri;
+ private final UserHandle mAssociatedUser;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -1081,6 +1083,16 @@ public final class Call {
return mCallerNumberVerificationStatus;
}
+ /**
+ * Gets the user that originated the call
+ * @return The user
+ *
+ * @hide
+ */
+ public UserHandle getAssociatedUser() {
+ return mAssociatedUser;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
@@ -1107,7 +1119,8 @@ public final class Call {
Objects.equals(mCallDirection, d.mCallDirection) &&
Objects.equals(mCallerNumberVerificationStatus,
d.mCallerNumberVerificationStatus) &&
- Objects.equals(mContactPhotoUri, d.mContactPhotoUri);
+ Objects.equals(mContactPhotoUri, d.mContactPhotoUri) &&
+ Objects.equals(mAssociatedUser, d.mAssociatedUser);
}
return false;
}
@@ -1133,7 +1146,8 @@ public final class Call {
mContactDisplayName,
mCallDirection,
mCallerNumberVerificationStatus,
- mContactPhotoUri);
+ mContactPhotoUri,
+ mAssociatedUser);
}
/** {@hide} */
@@ -1158,7 +1172,8 @@ public final class Call {
String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus,
- Uri contactPhotoUri) {
+ Uri contactPhotoUri,
+ UserHandle originatingUser) {
mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
@@ -1180,6 +1195,7 @@ public final class Call {
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
mContactPhotoUri = contactPhotoUri;
+ mAssociatedUser = originatingUser;
}
/** {@hide} */
@@ -1205,7 +1221,8 @@ public final class Call {
parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
parcelableCall.getCallerNumberVerificationStatus(),
- parcelableCall.getContactPhotoUri()
+ parcelableCall.getContactPhotoUri(),
+ parcelableCall.getAssociatedUser()
);
}
@@ -2631,7 +2648,8 @@ public final class Call {
mDetails.getContactDisplayName(),
mDetails.getCallDirection(),
mDetails.getCallerNumberVerificationStatus(),
- mDetails.getContactPhotoUri()
+ mDetails.getContactPhotoUri(),
+ mDetails.getAssociatedUser()
);
fireDetailsChanged(mDetails);
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 29d394201f39..ebe00782319a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -1237,6 +1237,10 @@ public abstract class Connection extends Conferenceable {
builder.append(isLong ? " PROPERTY_IS_DOWNGRADED_CONFERENCE" : " dngrd_conf");
}
+ if ((properties & PROPERTY_CROSS_SIM) == PROPERTY_CROSS_SIM) {
+ builder.append(isLong ? " PROPERTY_CROSS_SIM" : " xsim");
+ }
+
builder.append("]");
return builder.toString();
}
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 6a1318982e77..bd004e5e6231 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,14 +16,17 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.telecom.Call.Details.CallDirection;
import com.android.internal.telecom.IVideoProvider;
@@ -70,6 +73,7 @@ public final class ParcelableCall implements Parcelable {
private String mContactDisplayName;
private String mActiveChildCallId;
private Uri mContactPhotoUri;
+ private UserHandle mAssociatedUser;
public ParcelableCallBuilder setId(String id) {
mId = id;
@@ -230,6 +234,11 @@ public final class ParcelableCall implements Parcelable {
return this;
}
+ public ParcelableCallBuilder setAssociatedUser(UserHandle user) {
+ mAssociatedUser = user;
+ return this;
+ }
+
public ParcelableCall createParcelableCall() {
return new ParcelableCall(
mId,
@@ -262,7 +271,8 @@ public final class ParcelableCall implements Parcelable {
mCallerNumberVerificationStatus,
mContactDisplayName,
mActiveChildCallId,
- mContactPhotoUri);
+ mContactPhotoUri,
+ mAssociatedUser);
}
public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -300,6 +310,7 @@ public final class ParcelableCall implements Parcelable {
newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
newBuilder.mContactPhotoUri = parcelableCall.mContactPhotoUri;
+ newBuilder.mAssociatedUser = parcelableCall.mAssociatedUser;
return newBuilder;
}
}
@@ -336,6 +347,7 @@ public final class ParcelableCall implements Parcelable {
private final String mContactDisplayName;
private final String mActiveChildCallId; // Only valid for CDMA conferences
private final Uri mContactPhotoUri;
+ private final UserHandle mAssociatedUser;
public ParcelableCall(
String id,
@@ -368,7 +380,8 @@ public final class ParcelableCall implements Parcelable {
int callerNumberVerificationStatus,
String contactDisplayName,
String activeChildCallId,
- Uri contactPhotoUri
+ Uri contactPhotoUri,
+ UserHandle associatedUser
) {
mId = id;
mState = state;
@@ -401,6 +414,7 @@ public final class ParcelableCall implements Parcelable {
mContactDisplayName = contactDisplayName;
mActiveChildCallId = activeChildCallId;
mContactPhotoUri = contactPhotoUri;
+ mAssociatedUser = associatedUser;
}
/** The unique ID of the call. */
@@ -624,6 +638,13 @@ public final class ParcelableCall implements Parcelable {
return mContactPhotoUri;
}
+ /**
+ * @return the originating user
+ */
+ public @NonNull UserHandle getAssociatedUser() {
+ return mAssociatedUser;
+ }
+
/**
* @return On a CDMA conference with two participants, returns the ID of the child call that's
@@ -666,6 +687,9 @@ public final class ParcelableCall implements Parcelable {
source.readList(conferenceableCallIds, classLoader, java.lang.String.class);
Bundle intentExtras = source.readBundle(classLoader);
Bundle extras = source.readBundle(classLoader);
+ if (extras == null) {
+ extras = new Bundle();
+ }
int supportedAudioRoutes = source.readInt();
boolean isRttCallChanged = source.readByte() == 1;
ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class);
@@ -675,6 +699,8 @@ public final class ParcelableCall implements Parcelable {
String contactDisplayName = source.readString();
String activeChildCallId = source.readString();
Uri contactPhotoUri = source.readParcelable(classLoader, Uri.class);
+ UserHandle associatedUser = source.readParcelable(classLoader, UserHandle.class);
+ extras.putParcelable(Intent.EXTRA_USER_HANDLE, associatedUser);
return new ParcelableCallBuilder()
.setId(id)
.setState(state)
@@ -707,6 +733,7 @@ public final class ParcelableCall implements Parcelable {
.setContactDisplayName(contactDisplayName)
.setActiveChildCallId(activeChildCallId)
.setContactPhotoUri(contactPhotoUri)
+ .setAssociatedUser(associatedUser)
.createParcelableCall();
}
@@ -757,6 +784,7 @@ public final class ParcelableCall implements Parcelable {
destination.writeString(mContactDisplayName);
destination.writeString(mActiveChildCallId);
destination.writeParcelable(mContactPhotoUri, 0);
+ destination.writeParcelable(mAssociatedUser, 0);
}
@Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 58833e8f8141..50c5a6b68c7b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10962,7 +10962,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1);
sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40);
sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000);
- sDefaults.putInt(KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT, 3 * 1000);
+ sDefaults.putInt(KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT, -1);
sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, "");
sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, "");
sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, "");
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index e6515f13b858..850ce3e2bbdc 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,7 +38,6 @@ import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
import android.telephony.data.Qos;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
@@ -89,35 +87,30 @@ public final class PreciseDataConnectionState implements Parcelable {
* Unsupported. The unsupported state is used when the data network cannot support the network
* validation function for the current data connection state.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public static final int NETWORK_VALIDATION_UNSUPPORTED = 0;
/**
* Not Requested. The not requested status is used when the data network supports the network
* validation function, but no network validation is being performed yet.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1;
/**
* In progress. The in progress state is used when the network validation process for the data
* network is in progress. This state is followed by either success or failure.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public static final int NETWORK_VALIDATION_IN_PROGRESS = 2;
/**
* Success. The Success status is used when network validation has been completed for the data
* network and the result is successful.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public static final int NETWORK_VALIDATION_SUCCESS = 3;
/**
* Failure. The Failure status is used when network validation has been completed for the data
* network and the result is failure.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public static final int NETWORK_VALIDATION_FAILURE = 4;
/**
@@ -360,7 +353,6 @@ public final class PreciseDataConnectionState implements Parcelable {
*
* @return the network validation status of the data call
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public @NetworkValidationStatus int getNetworkValidationStatus() {
return mNetworkValidationStatus;
}
@@ -615,7 +607,6 @@ public final class PreciseDataConnectionState implements Parcelable {
* @param networkValidationStatus the network validation status of the data call
* @return The builder
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public @NonNull Builder setNetworkValidationStatus(
@NetworkValidationStatus int networkValidationStatus) {
mNetworkValidationStatus = networkValidationStatus;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d2741ac7ee9f..e04d285d6632 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1187,6 +1187,61 @@ public class SubscriptionManager {
public static final String IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM =
SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM;
+ /**
+ * TelephonyProvider column name for satellite entitlement barred plmns. The value of this
+ * column is set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ * <P>Type: TEXT </P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_BARRED_PLMNS =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_BARRED_PLMNS;
+
+ /**
+ * TelephonyProvider column name for satellite entitlement data plan for plmns. The value
+ * of this column is set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ * <P>Type: TEXT </P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_DATA_PLAN_PLMNS =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_DATA_PLAN_PLMNS;
+
+ /**
+ * TelephonyProvider column name for satellite entitlement service type map. The value of
+ * this column is set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ * <P>Type: TEXT </P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_SERVICE_TYPE_MAP =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_SERVICE_TYPE_MAP;
+
+ /**
+ * TelephonyProvider column name for satellite entitlement data service policy. The value
+ * of this column is set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ * <P>Type: TEXT </P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_DATA_SERVICE_POLICY =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_DATA_SERVICE_POLICY;
+
+ /**
+ * TelephonyProvider column name for satellite entitlement voice service policy. The value
+ * of this column is set based on entitlement query result for satellite configuration.
+ * By default, it's empty.
+ * <P>Type: TEXT </P>
+ *
+ * @hide
+ */
+ public static final String SATELLITE_ENTITLEMENT_VOICE_SERVICE_POLICY =
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_VOICE_SERVICE_POLICY;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index 7356cdc8889b..42d09cfc2e43 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -31,7 +31,6 @@ import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsManager;
import android.telephony.satellite.SatelliteManager;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.Preconditions;
@@ -77,9 +76,6 @@ public class TelephonyFrameworkInitializer {
// also check through Compatibility framework a few lines below).
@SuppressWarnings("AndroidFrameworkCompatChange")
private static boolean hasSystemFeature(Context context, String feature) {
- // Check release status of this change in behavior.
- if (!Flags.minimalTelephonyManagersConditionalOnFeatures()) return true;
-
// Check SDK version of the vendor partition.
final int vendorApiLevel = SystemProperties.getInt(
"ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fbba999bfb36..2983e4442a78 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -691,7 +691,7 @@ public class TelephonyManager {
case UNKNOWN:
modemCount = 1;
// check for voice and data support, 0 if not supported
- if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+ if (!isDeviceVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
modemCount = 0;
}
break;
@@ -2814,7 +2814,7 @@ public class TelephonyManager {
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public int getPhoneType() {
- if (!isVoiceCapable() && !isDataCapable()) {
+ if (!isDeviceVoiceCapable() && !isDataCapable()) {
return PHONE_TYPE_NONE;
}
return getCurrentPhoneType();
@@ -3371,14 +3371,13 @@ public class TelephonyManager {
return telephony.getDataNetworkTypeForSubscriber(subId, getOpPackageName(),
getAttributionTag());
} else {
- // This can happen when the ITelephony interface is not up yet.
+ Log.e(TAG, "getDataNetworkType: ITelephony interface is not up yet");
return NETWORK_TYPE_UNKNOWN;
}
- } catch(RemoteException ex) {
- // This shouldn't happen in the normal case
- return NETWORK_TYPE_UNKNOWN;
- } catch (NullPointerException ex) {
- // This could happen before phone restarts due to crashing
+ } catch (RemoteException // Shouldn't happen in the normal case
+ | NullPointerException ex // Could happen before phone restarts due to crashing
+ ) {
+ Log.e(TAG, "getDataNetworkType: " + ex.getMessage());
return NETWORK_TYPE_UNKNOWN;
}
}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index b6f9e1f4c3af..0e7030688c70 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -17,7 +17,6 @@
package android.telephony.data;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -32,7 +31,6 @@ import android.telephony.PreciseDataConnectionState;
import android.telephony.data.ApnSetting.ProtocolType;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -455,7 +453,6 @@ public final class DataCallResponse implements Parcelable {
*
* @return The network validation status of data connection.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public @PreciseDataConnectionState.NetworkValidationStatus int getNetworkValidationStatus() {
return mNetworkValidationStatus;
}
@@ -936,7 +933,6 @@ public final class DataCallResponse implements Parcelable {
* @param status The network validation status.
* @return The same instance of the builder.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public @NonNull Builder setNetworkValidationStatus(
@PreciseDataConnectionState.NetworkValidationStatus int status) {
mNetworkValidationStatus = status;
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index f04e1c9b221d..5baf46388fc4 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -17,7 +17,6 @@
package android.telephony.data;
import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -40,7 +39,6 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IIntegerConsumer;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
@@ -414,7 +412,6 @@ public abstract class DataService extends Service {
* @param resultCodeCallback Listener for the {@link DataServiceCallback.ResultCode} that
* request validation to the DataService and checks if the request has been submitted.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public void requestNetworkValidation(int cid,
@NonNull @CallbackExecutor Executor executor,
@NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) {
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index f775de6ebef4..c42b29c143aa 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -17,7 +17,6 @@
package android.telephony.data;
import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.Service;
@@ -41,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.flags.FeatureFlagsImpl;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
@@ -290,7 +288,6 @@ public abstract class QualifiedNetworksService extends Service {
* @param resultCodeCallback A callback to determine whether the request was successfully
* submitted or not.
*/
- @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public void requestNetworkValidation(
@NetCapability int networkCapability,
@NonNull @CallbackExecutor Executor executor,
@@ -298,15 +295,6 @@ public abstract class QualifiedNetworksService extends Service {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null");
- if (!sFeatureFlag.networkValidation()) {
- loge("networkValidation feature is disabled");
- executor.execute(
- () ->
- resultCodeCallback.accept(
- DataServiceCallback.RESULT_ERROR_UNSUPPORTED));
- return;
- }
-
IIntegerConsumer callback = new IIntegerConsumer.Stub() {
@Override
public void accept(int result) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 1c6652daf498..0dd0a42d44b4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3203,6 +3203,31 @@ interface ITelephony {
in List<String> satelliteCountryCodes, String satelliteAccessConfigurationFile);
/**
+ * This API can be used by only CTS to override the satellite access allowed state for
+ * a list of subscription IDs.
+ *
+ * @param subIdListStr The string representation of the list of subscription IDs,
+ * which are numbers separated by comma.
+ * @return {@code true} if the satellite access allowed state is set successfully,
+ * {@code false} otherwise.
+ */
+ boolean setSatelliteAccessAllowedForSubscriptions(in String subIdListStr);
+
+ /**
+ * This API can be used by only CTS to override satellite TN scanning support.
+ *
+ * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+ * otherwise.
+ * @param concurrentTnScanningSupported Whether concurrent TN scanning is supported.
+ * @param tnScanningDuringSatelliteSessionAllowed Whether TN scanning is allowed during
+ * a satellite session.
+ * @return {@code true} if the TN scanning support is set successfully,
+ * {@code false} otherwise.
+ */
+ boolean setTnScanningSupport(in boolean reset, in boolean concurrentTnScanningSupported,
+ in boolean tnScanningDuringSatelliteSessionAllowed);
+
+ /**
* This API can be used in only testing to override oem-enabled satellite provision status.
*
* @param reset {@code true} mean the overriding status should not be used, {@code false}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
index 3498974b348e..229c7bfb53e9 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -103,6 +103,10 @@ public class IntegrationTests {
@RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
public void reportJankStats_confirmPendingStatsIncreases() {
Activity jankTrackerActivity = mJankTrackerActivityRule.launchActivity(null);
+ mDevice.wait(Until.findObject(
+ By.text(jankTrackerActivity.getString(R.string.continue_test))),
+ WAIT_FOR_TIMEOUT_MS);
+
EditText editText = jankTrackerActivity.findViewById(R.id.edit_text);
JankTracker jankTracker = editText.getJankTracker();
@@ -135,6 +139,10 @@ public class IntegrationTests {
public void simulateWidgetStateChanges_confirmStateChangesAreTracked() {
JankTrackerActivity jankTrackerActivity =
mJankTrackerActivityRule.launchActivity(null);
+ mDevice.wait(Until.findObject(
+ By.text(jankTrackerActivity.getString(R.string.continue_test))),
+ WAIT_FOR_TIMEOUT_MS);
+
TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget);
JankTracker jankTracker = testWidget.getJankTracker();
jankTracker.forceListenerRegistration();
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 685ae9a5fef2..c5e7188e6efe 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 5f92d7fe830b..22bd458e2751 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 1b90e99a8ba2..541ce26d1435 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index ffdbb02984a7..d2e02193f0fb 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index ac704e5e7c39..e112c82f0661 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -49,6 +49,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index e2ac5a9579ae..e5700c03cf77 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 1a4feb6e9eca..4c41a4c01180 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 1b2007deae27..27fc249e36b9 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
<!-- Disable AOD -->
<option name="run-command" value="settings put secure doze_always_on 0"/>
+ <!-- Disable explore hub mode -->
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index d7f91e009c92..2093ccc86653 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -156,19 +156,6 @@ class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(f
flicker.assertLayers { isVisible(testApp) }
}
- /** Checks that [testApp] layer covers the entire screen during the whole transition */
- @Presubmit
- @Test
- fun appLayerRotates() {
- flicker.assertLayers {
- this.invoke("entireScreenCovered") { entry ->
- entry.entry.displays.map { display ->
- entry.visibleRegion(testApp).coversExactly(display.layerStackSpace)
- }
- }
- }
- }
-
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. App is full screen")
@@ -225,7 +212,6 @@ class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(f
visibleLayersShownMoreThanOneConsecutiveEntry()
visibleWindowsShownMoreThanOneConsecutiveEntry()
- runAndIgnoreAssumptionViolation { appLayerRotates() }
runAndIgnoreAssumptionViolation { appLayerAlwaysVisible() }
runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() }
runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 55d6fd9b4a73..6d8ea409f6f2 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -28,10 +28,13 @@ import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.wm.WindowingMode
+import android.view.KeyEvent.KEYCODE_DPAD_DOWN
+import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.KeyEvent.KEYCODE_EQUALS
import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
import android.view.KeyEvent.KEYCODE_MINUS
import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
+import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
import android.view.WindowInsets
import android.view.WindowManager
@@ -151,19 +154,42 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
?: error("Unable to find resource $MAXIMIZE_BUTTON_VIEW\n")
}
- /** Click maximise button on the app header for the given app. */
+ /** Maximize a given app to fill the stable bounds. */
fun maximiseDesktopApp(
wmHelper: WindowManagerStateHelper,
device: UiDevice,
- usingKeyboard: Boolean = false
+ trigger: MaximizeDesktopAppTrigger = MaximizeDesktopAppTrigger.MAXIMIZE_MENU,
) {
- if (usingKeyboard) {
- val keyEventHelper = KeyEventHelper(getInstrumentation())
- keyEventHelper.press(KEYCODE_EQUALS, META_META_ON)
- } else {
- val caption = getCaptionForTheApp(wmHelper, device)
- val maximizeButton = getMaximizeButtonForTheApp(caption)
- maximizeButton.click()
+ val caption = getCaptionForTheApp(wmHelper, device)!!
+ val maximizeButton = getMaximizeButtonForTheApp(caption)
+
+ when (trigger) {
+ MaximizeDesktopAppTrigger.MAXIMIZE_MENU -> maximizeButton.click()
+ MaximizeDesktopAppTrigger.DOUBLE_TAP_APP_HEADER -> {
+ caption.click()
+ Thread.sleep(50)
+ caption.click()
+ }
+
+ MaximizeDesktopAppTrigger.KEYBOARD_SHORTCUT -> {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_EQUALS, META_META_ON)
+ }
+
+ MaximizeDesktopAppTrigger.MAXIMIZE_BUTTON_IN_MENU -> {
+ maximizeButton.longClick()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ val buttonResId = MAXIMIZE_BUTTON_IN_MENU
+ val maximizeMenu = getDesktopAppViewByRes(MAXIMIZE_MENU)
+ val maximizeButtonInMenu =
+ maximizeMenu
+ ?.wait(
+ Until.findObject(By.res(SYSTEMUI_PACKAGE, buttonResId)),
+ TIMEOUT.toMillis()
+ )
+ ?: error("Unable to find object with resource id $buttonResId")
+ maximizeButtonInMenu.click()
+ }
}
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
@@ -472,6 +498,22 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
device.drag(startX, startY, endX, endY, 100)
}
+ fun enterDesktopModeViaKeyboard(
+ wmHelper: WindowManagerStateHelper,
+ ) {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_DPAD_DOWN, META_META_ON or META_CTRL_ON)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ fun exitDesktopModeToFullScreenViaKeyboard(
+ wmHelper: WindowManagerStateHelper,
+ ) {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_DPAD_UP, META_META_ON or META_CTRL_ON)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
fun enterDesktopModeFromAppHandleMenu(
wmHelper: WindowManagerStateHelper,
device: UiDevice
@@ -550,6 +592,13 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
rightSideMatching
}
+ enum class MaximizeDesktopAppTrigger {
+ MAXIMIZE_MENU,
+ DOUBLE_TAP_APP_HEADER,
+ KEYBOARD_SHORTCUT,
+ MAXIMIZE_BUTTON_IN_MENU
+ }
+
private companion object {
val TIMEOUT: Duration = Duration.ofSeconds(3)
const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
@@ -561,6 +610,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
const val DESKTOP_MODE_BUTTON: String = "desktop_button"
const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+ const val MAXIMIZE_BUTTON_IN_MENU: String = "maximize_menu_size_toggle_button"
const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
const val HEADER_EMPTY_VIEW: String = "caption_handle"
val caption: BySelector
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 7c24a4adca3d..98af8b2f53b5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -39,7 +39,6 @@
android:value="true" />
<activity android:name=".SimpleActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
- android:theme="@style/CutoutShortEdges"
android:label="SimpleActivity"
android:exported="true">
<intent-filter>
@@ -49,7 +48,6 @@
</activity>
<activity android:name=".ImeActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
- android:theme="@style/CutoutShortEdges"
android:label="ImeActivity"
android:exported="true">
<intent-filter>
@@ -58,7 +56,6 @@
</intent-filter>
</activity>
<activity android:name=".ImeActivityAutoFocus"
- android:theme="@style/CutoutShortEdges"
android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
android:windowSoftInputMode="stateVisible"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
@@ -81,7 +78,6 @@
</activity>
<activity android:name=".SeamlessRotationActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
android:showWhenLocked="true"
android:label="SeamlessActivity"
@@ -92,7 +88,6 @@
</intent-filter>
</activity>
<activity android:name=".NonResizeableActivity"
- android:theme="@style/CutoutShortEdges"
android:resizeableActivity="false"
android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
android:label="NonResizeableActivity"
@@ -174,7 +169,6 @@
</activity>
<activity android:name=".LaunchNewActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize"
android:label="LaunchNewActivity"
android:exported="true">
@@ -185,7 +179,6 @@
</activity>
<activity android:name=".LaunchNewTaskActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize"
android:label="LaunchNewTaskActivity"
android:exported="true">
@@ -207,7 +200,6 @@
</activity>
<activity android:name=".PortraitOnlyActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
- android:theme="@style/CutoutShortEdges"
android:screenOrientation="portrait"
android:configChanges="orientation|screenSize"
android:exported="true">
@@ -219,7 +211,6 @@
<activity android:name=".ImeEditorPopupDialogActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
android:configChanges="orientation|screenSize"
- android:theme="@style/CutoutShortEdges"
android:label="ImeEditorPopupDialogActivity"
android:exported="true">
<intent-filter>
@@ -229,7 +220,6 @@
</activity>
<activity android:name=".ShowWhenLockedActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize"
android:label="ShowWhenLockedActivity"
android:showWhenLocked="true"
@@ -241,7 +231,6 @@
</activity>
<activity android:name=".NotificationActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize"
android:label="NotificationActivity"
android:exported="true">
@@ -254,7 +243,6 @@
android:name=".ActivityEmbeddingMainActivity"
android:label="ActivityEmbedding Main"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true">
<intent-filter>
@@ -266,7 +254,6 @@
android:name=".ActivityEmbeddingTrampolineActivity"
android:label="ActivityEmbedding Trampoline"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false">
</activity>
@@ -274,7 +261,6 @@
android:name=".ActivityEmbeddingSecondaryActivity"
android:label="ActivityEmbedding Secondary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:supportsPictureInPicture="true"
android:exported="false"/>
@@ -282,21 +268,18 @@
android:name=".ActivityEmbeddingThirdActivity"
android:label="ActivityEmbedding Third"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity
android:name=".ActivityEmbeddingAlwaysExpandActivity"
android:label="ActivityEmbedding AlwaysExpand"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity
android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
android:label="ActivityEmbedding Placeholder Primary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false">
</activity>
@@ -304,7 +287,6 @@
android:name=".ActivityEmbeddingPlaceholderSecondaryActivity"
android:label="ActivityEmbedding Placeholder Secondary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
- android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity android:name=".MailActivity"
@@ -334,7 +316,6 @@
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity"
- android:theme="@style/CutoutShortEdges"
android:launchMode="singleTop"
android:label="PipActivity"
android:exported="true">
@@ -350,7 +331,6 @@
<activity android:name=".BottomHalfPipLaunchingActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
- android:theme="@style/CutoutShortEdges"
android:label="BottomHalfPipLaunchingActivity"
android:exported="true">
<intent-filter>
@@ -371,7 +351,6 @@
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
- android:theme="@style/CutoutShortEdges"
android:label="SplitScreenPrimaryActivity"
android:exported="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
@@ -383,7 +362,6 @@
<activity android:name=".SplitScreenSecondaryActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity"
- android:theme="@style/CutoutShortEdges"
android:label="SplitScreenSecondaryActivity"
android:exported="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
@@ -396,7 +374,6 @@
</activity>
<activity android:name=".SendNotificationActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
- android:theme="@style/CutoutShortEdges"
android:label="SendNotificationActivity"
android:exported="true">
<intent-filter>
@@ -408,7 +385,6 @@
android:name=".LaunchBubbleActivity"
android:label="LaunchBubbleActivity"
android:exported="true"
- android:theme="@style/CutoutShortEdges"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -420,7 +396,6 @@
android:name=".BubbleActivity"
android:label="BubbleActivity"
android:exported="false"
- android:theme="@style/CutoutShortEdges"
android:resizeableActivity="true"/>
<activity
android:name=".TransferSplashscreenActivity"
@@ -468,4 +443,4 @@
</service>
</application>
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
-</manifest>
+</manifest> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml
index 2f9c3aa82057..15e2a798b2cf 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bottom_half_pip.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@android:color/holo_blue_bright">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
index 7c7b2cacaefb..4bc70996eba8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
@@ -16,6 +16,7 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
index f0dfdfce035f..e98ffd045d3d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
@@ -19,5 +19,6 @@
android:id="@+id/root_activity_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical">
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 939ba81a47ea..16c906d9ee42 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:background="@android:color/holo_orange_light">
<LinearLayout
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
index 6d4de995bd73..eedb910b2b4e 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -19,6 +19,7 @@
android:id="@+id/secondary_activity_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical">
<Button
@@ -49,4 +50,4 @@
android:layout_height="48dp"
android:text="Enter pip" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index 507c1b622613..5e5203cb0545 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -17,6 +17,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_green_light"
+ android:fitsSystemWindows="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
index fe7bced690f9..2ab5fe7b9d6c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:background="@android:color/holo_orange_light">
<Button
android:id="@+id/launch_second_activity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
index 8a97433ede04..fcef791afe2e 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml
@@ -19,6 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
+ android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
index 6d5a9dd29248..aeb8423680a3 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@android:color/holo_orange_light">
@@ -29,4 +30,4 @@
android:text="NonResizeableActivity"
android:textAppearance="?android:attr/textAppearanceLarge"/>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 365a0ea017f6..d66b3d74e114 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@android:color/holo_blue_bright">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml
index 5d94e5177dcc..42213d6812da 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:background="@android:color/holo_orange_light">
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
index 79e88e49b99e..16c3bc07d55c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@android:color/holo_green_light">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
index ed9feaf1eade..7643cf5de216 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@android:color/holo_blue_light">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
index c34d2003ef42..79e7bb5de96e 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:gravity="center"
android:orientation="vertical"
android:background="@android:color/holo_orange_light">
@@ -39,4 +40,4 @@
android:gravity="center_vertical|center_horizontal"
android:text="Start Media Projection with extra intent"
android:textAppearance="?android:attr/textAppearanceLarge"/>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml
index 0b4693dec6e1..4d12168ca8db 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml
@@ -19,6 +19,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:background="@android:color/holo_orange_light">
<SurfaceView
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
index 0730ded66ce4..32df5f015414 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
index ff4ead95f16e..a0c87fd17fc3 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
@@ -17,6 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical"
android:background="@android:color/black">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml
index 78072006f681..da58b3f43c73 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml
@@ -18,10 +18,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:background="@android:color/holo_orange_light">
<Button
android:id="@+id/post_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|center_vertical"
android:text="Post Notification" />
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
index 8f75d175d00a..ec3135c0a42d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:background="@android:color/holo_orange_light">
<Button
android:id="@+id/launch_new_task"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 837d050b73ff..ca3244820893 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -36,10 +36,6 @@
<item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
- <style name="CutoutShortEdges" parent="@style/DefaultTheme">
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
- </style>
-
<style name="CutoutNever" parent="@style/DefaultTheme">
<item name="android:windowLayoutInDisplayCutoutMode">never</item>
</style>
@@ -78,4 +74,4 @@
<!-- Here we want to match the duration of our AVD -->
<item name="android:windowSplashScreenAnimationDuration">900</item>
</style>
-</resources>
+</resources> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
index 4418b5a5ff82..30bf616cfe74 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
@@ -30,7 +30,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
-import android.view.WindowManager;
public class ImeActivity extends Activity {
@@ -64,10 +63,6 @@ public class ImeActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.activity_ime);
final var filter = new IntentFilter();
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
index 95f933f97bb2..887a15c9ea90 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
@@ -30,8 +30,6 @@ public class ImeEditorPopupDialogActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
p.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN;
getWindow().setAttributes(p);
LinearLayout layout = new LinearLayout(this);
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
index e5710c850f07..97d7a64d5ebf 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
@@ -19,17 +19,12 @@ package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.view.WindowManager;
import android.widget.Button;
public class LaunchNewActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.activity_launch_new);
Button button = findViewById(R.id.launch_second_activity);
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
index 1809781b33e6..402a393e7028 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
@@ -21,17 +21,12 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.view.WindowManager;
import android.widget.Button;
public class LaunchNewTaskActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.task_button);
Button button = findViewById(R.id.launch_new_task);
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
index d6427abcc65a..61254385e980 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
@@ -45,14 +45,10 @@ public class NotificationActivity extends Activity {
requestPermissions(new String[] { POST_NOTIFICATIONS }, 0);
}
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.notification_button);
- Button button = findViewById(R.id.post_notification);
- button.setOnClickListener(v -> postNotification());
+ ((Button) findViewById(R.id.post_notification))
+ .setOnClickListener(v -> postNotification());
createNotificationChannel();
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index ee25ab2fb66c..e030dcf0db9b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -47,8 +47,6 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Rational;
import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.RadioButton;
@@ -145,12 +143,6 @@ public class PipActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final Window window = getWindow();
- final WindowManager.LayoutParams layoutParams = window.getAttributes();
- layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- window.setAttributes(layoutParams);
-
setContentView(R.layout.activity_pip);
findViewById(R.id.media_session_start)
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java
index b1876b5e5511..552d843676bb 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java
@@ -18,16 +18,11 @@ package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.os.Bundle;
-import android.view.WindowManager;
public class PortraitOnlyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.activity_simple);
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
index ce7a0059fa2d..e98c34db0cd9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
@@ -61,8 +61,6 @@ public class SeamlessRotationActivity extends Activity {
private void enableSeamlessRotation() {
WindowManager.LayoutParams p = getWindow().getAttributes();
p.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java
index 6f94b74ccf41..a533c9052574 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java
@@ -18,16 +18,11 @@ package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.os.Bundle;
-import android.view.WindowManager;
public class ShowWhenLockedActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.activity_simple);
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java
index 699abf87d341..c56eefe32189 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java
@@ -18,16 +18,11 @@ package com.android.server.wm.flicker.testapp;
import android.app.Activity;
import android.os.Bundle;
-import android.view.WindowManager;
public class SimpleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
setContentView(R.layout.activity_simple);
}
}
diff --git a/tests/Input/assets/testPointerScale.png b/tests/Input/assets/testPointerScale.png
index 54d37c24afc6..781df47a5e24 100644
--- a/tests/Input/assets/testPointerScale.png
+++ b/tests/Input/assets/testPointerScale.png
Binary files differ
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
index e99c81493394..794fd0255726 100644
--- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
@@ -214,9 +214,5 @@ class KeyGestureEventHandlerTest {
): Boolean {
return handler(event, focusedToken)
}
-
- override fun isKeyGestureSupported(gestureType: Int): Boolean {
- return true
- }
}
}
diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
index 044f11d6904c..890c346ea015 100644
--- a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
@@ -184,6 +184,8 @@ class BatteryControllerTests {
@get:Rule
val rule = MockitoJUnit.rule()!!
@get:Rule
+ val context = TestableContext(ApplicationProvider.getApplicationContext())
+ @get:Rule
val inputManagerRule = MockInputManagerRule()
@Mock
@@ -194,7 +196,6 @@ class BatteryControllerTests {
private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
private lateinit var batteryController: BatteryController
- private lateinit var context: TestableContext
private lateinit var testLooper: TestLooper
private lateinit var devicesChangedListener: IInputDevicesChangedListener
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@@ -202,7 +203,6 @@ class BatteryControllerTests {
@Before
fun setup() {
- context = TestableContext(ApplicationProvider.getApplicationContext())
testLooper = TestLooper()
val inputManager = InputManager(context)
context.addMockSystemService(InputManager::class.java, inputManager)
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index c666fb7e05f1..4f1fb6487b19 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -32,6 +32,7 @@ import android.hardware.input.InputGestureData
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
import android.hardware.input.KeyGestureEvent
+import android.os.Handler
import android.os.IBinder
import android.os.Process
import android.os.SystemClock
@@ -48,9 +49,11 @@ import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE
import androidx.test.core.app.ApplicationProvider
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.R
+import com.android.internal.accessibility.AccessibilityShortcutController
import com.android.internal.annotations.Keep
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.server.input.InputManagerService.WindowManagerCallbacks
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
@@ -67,6 +70,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
/**
* Tests for {@link KeyGestureController}.
@@ -102,6 +107,7 @@ class KeyGestureControllerTests {
const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0
const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1
const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2
+ const val TEST_PID = 10
}
@JvmField
@@ -116,11 +122,10 @@ class KeyGestureControllerTests {
@Rule
val rule = SetFlagsRule()
- @Mock
- private lateinit var iInputManager: IInputManager
-
- @Mock
- private lateinit var packageManager: PackageManager
+ @Mock private lateinit var iInputManager: IInputManager
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var wmCallbacks: WindowManagerCallbacks
+ @Mock private lateinit var accessibilityShortcutController: AccessibilityShortcutController
private var currentPid = 0
private lateinit var context: Context
@@ -207,8 +212,34 @@ class KeyGestureControllerTests {
private fun setupKeyGestureController() {
keyGestureController =
- KeyGestureController(context, testLooper.looper, testLooper.looper, inputDataStore)
- Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ KeyGestureController(
+ context,
+ testLooper.looper,
+ testLooper.looper,
+ inputDataStore,
+ object : KeyGestureController.Injector() {
+ override fun getAccessibilityShortcutController(
+ context: Context?,
+ handler: Handler?
+ ): AccessibilityShortcutController {
+ return accessibilityShortcutController
+ }
+ })
+ Mockito.`when`(iInputManager.registerKeyGestureHandler(Mockito.any()))
+ .thenAnswer {
+ val args = it.arguments
+ if (args[0] != null) {
+ keyGestureController.registerKeyGestureHandler(
+ args[0] as IKeyGestureHandler,
+ TEST_PID
+ )
+ }
+ }
+ keyGestureController.setWindowManagerCallbacks(wmCallbacks)
+ Mockito.`when`(wmCallbacks.isKeyguardLocked(Mockito.anyInt())).thenReturn(false)
+ Mockito.`when`(accessibilityShortcutController
+ .isAccessibilityShortcutAvailable(Mockito.anyBoolean())).thenReturn(true)
+ Mockito.`when`(iInputManager.appLaunchBookmarks)
.thenReturn(keyGestureController.appLaunchBookmarks)
keyGestureController.systemRunning()
testLooper.dispatchAll()
@@ -382,14 +413,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + DEL -> Back",
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DEL),
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- intArrayOf(KeyEvent.KEYCODE_DEL),
- KeyEvent.META_META_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
"META + ESC -> Back",
intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ESCAPE),
KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
@@ -1278,9 +1301,9 @@ class KeyGestureControllerTests {
)
),
TestData(
- "BACK + DPAD_DOWN -> TV Accessibility Chord",
+ "BACK + DPAD_DOWN -> Accessibility Chord(for TV)",
intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
- KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
0,
intArrayOf(
@@ -1630,6 +1653,52 @@ class KeyGestureControllerTests {
)
}
+ @Test
+ fun testAccessibilityShortcutChordPressed() {
+ setupKeyGestureController()
+
+ sendKeys(
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN),
+ // Assuming this value is always greater than the accessibility shortcut timeout, which
+ // currently defaults to 3000ms
+ timeDelayMs = 10000
+ )
+ Mockito.verify(accessibilityShortcutController, times(1)).performAccessibilityShortcut()
+ }
+
+ @Test
+ fun testAccessibilityTvShortcutChordPressed() {
+ setupKeyGestureController()
+
+ sendKeys(
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
+ timeDelayMs = 10000
+ )
+ Mockito.verify(accessibilityShortcutController, times(1)).performAccessibilityShortcut()
+ }
+
+ @Test
+ fun testAccessibilityShortcutChordPressedForLessThanTimeout() {
+ setupKeyGestureController()
+
+ sendKeys(
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN),
+ timeDelayMs = 0
+ )
+ Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut()
+ }
+
+ @Test
+ fun testAccessibilityTvShortcutChordPressedForLessThanTimeout() {
+ setupKeyGestureController()
+
+ sendKeys(
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
+ timeDelayMs = 0
+ )
+ Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut()
+ }
+
private fun testKeyGestureInternal(test: TestData) {
val handledEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
@@ -1691,7 +1760,11 @@ class KeyGestureControllerTests {
assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.size)
}
- private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) {
+ private fun sendKeys(
+ testKeys: IntArray,
+ assertNotSentToApps: Boolean = false,
+ timeDelayMs: Long = 0
+ ) {
var metaState = 0
val now = SystemClock.uptimeMillis()
for (key in testKeys) {
@@ -1707,6 +1780,11 @@ class KeyGestureControllerTests {
testLooper.dispatchAll()
}
+ if (timeDelayMs > 0) {
+ testLooper.moveTimeForward(timeDelayMs)
+ testLooper.dispatchAll()
+ }
+
for (key in testKeys.reversed()) {
val upEvent = KeyEvent(
now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState,
@@ -1750,9 +1828,5 @@ class KeyGestureControllerTests {
override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?): Boolean {
return handler(event, token)
}
-
- override fun isKeyGestureSupported(gestureType: Int): Boolean {
- return true
- }
}
}
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
index 5875520cd259..20528f23cc8c 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.testing.AndroidTestingRunner;
@@ -60,9 +59,12 @@ public class TouchpadDebugViewControllerTests {
private static final String TAG = "TouchpadDebugViewController";
@Rule
+ public final TestableContext mTestableContext =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Rule
public final MockitoRule mockito = MockitoJUnit.rule();
- private Context mContext;
private TouchpadDebugViewController mTouchpadDebugViewController;
@Mock
private InputManager mInputManagerMock;
@@ -74,8 +76,6 @@ public class TouchpadDebugViewControllerTests {
@Before
public void setup() throws Exception {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- TestableContext mTestableContext = new TestableContext(mContext);
mTestableContext.addMockSystemService(WindowManager.class, mWindowManagerMock);
Rect bounds = new Rect(0, 0, 2560, 1600);
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 60fa52f85e34..1c366a134300 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
@@ -51,6 +50,7 @@ import com.android.server.input.TouchpadHardwareProperties;
import com.android.server.input.TouchpadHardwareState;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -70,6 +70,10 @@ public class TouchpadDebugViewTest {
private TouchpadDebugView mTouchpadDebugView;
private WindowManager.LayoutParams mWindowLayoutParams;
+ @Rule
+ public final TestableContext mTestableContext =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+
@Mock
WindowManager mWindowManager;
@Mock
@@ -77,14 +81,10 @@ public class TouchpadDebugViewTest {
Rect mWindowBounds;
WindowMetrics mWindowMetrics;
- TestableContext mTestableContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
- mTestableContext = new TestableContext(context);
-
mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
mTestableContext.addMockSystemService(InputManager.class, mInputManager);
diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
index d16e90e26aaa..9d60e7c8b0b0 100644
--- a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
+++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
@@ -20,7 +20,7 @@ import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStat
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -123,7 +123,7 @@ public class AppLocaleCollectorTest {
doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales();
doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales();
doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale(
- anyObject(), eq(null), eq(true));
+ any(), eq(null), eq(true));
doReturn(mSystemCurrentLocales).when(
mAppLocaleCollector).getSystemCurrentLocales();
@@ -159,7 +159,7 @@ public class AppLocaleCollectorTest {
doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales();
doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales();
doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale(
- anyObject(), eq(null), eq(true));
+ any(), eq(null), eq(true));
doReturn(mSystemCurrentLocales).when(
mAppLocaleCollector).getSystemCurrentLocales();
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 16c6e3baf051..46cb5b7482e9 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -55,4 +55,8 @@ android_test {
"mts-crashrecovery",
],
min_sdk_version: "36",
+
+ // Test coverage system runs on different devices. Need to
+ // compile for all architecture.
+ compile_multilib: "both",
}
diff --git a/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java b/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java
new file mode 100644
index 000000000000..eda5e8613dba
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/RescuePartyTest.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
+import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
+import static com.android.server.RescueParty.DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN;
+import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.RecoverySystem;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.RescueParty.RescuePartyObserver;
+import com.android.server.am.SettingsToPropertiesMapper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Test RescueParty.
+ */
+public class RescuePartyTest {
+ private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
+
+ private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
+ private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+ private static final String PERSISTENT_PACKAGE = "com.persistent.package";
+ private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
+ private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
+ "persist.device_config.configuration.disable_rescue_party_factory_reset";
+
+ private MockitoSession mSession;
+ private HashMap<String, String> mSystemSettingsMap;
+ private HashMap<String, String> mCrashRecoveryPropertiesMap;
+ //Records the namespaces wiped by setProperties().
+ private HashSet<String> mNamespacesWiped;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mMockContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageWatchdog mMockPackageWatchdog;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ContentResolver mMockContentResolver;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManager mPackageManager;
+
+ // Mock only sysprop apis
+ private PackageWatchdog.BootThreshold mSpyBootThreshold;
+
+ @Before
+ public void setUp() throws Exception {
+ mSession =
+ ExtendedMockito.mockitoSession().initMocks(
+ this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(Settings.Global.class)
+ .spyStatic(Settings.Secure.class)
+ .spyStatic(SettingsToPropertiesMapper.class)
+ .spyStatic(RecoverySystem.class)
+ .spyStatic(RescueParty.class)
+ .spyStatic(PackageWatchdog.class)
+ .startMocking();
+ mSystemSettingsMap = new HashMap<>();
+ mNamespacesWiped = new HashSet<>();
+
+ when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
+ persistentApplicationInfo.flags |=
+ ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
+
+ // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
+ // system. Don't set any flags otherwise.
+ when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(persistentApplicationInfo);
+ when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(new ApplicationInfo());
+ // Reset observer instance to get new mock context on every run
+ RescuePartyObserver.reset();
+
+ // Mock SystemProperties setter and various getters
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ String value = invocationOnMock.getArgument(1);
+
+ mSystemSettingsMap.put(key, value);
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ boolean defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
+ }
+ ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ int defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
+ }
+ ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ long defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Long.parseLong(storedValue);
+ }
+ ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+ // Mock DeviceConfig
+ doAnswer((Answer<Boolean>) invocationOnMock -> true)
+ .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
+ anyBoolean()));
+ doAnswer((Answer<Void>) invocationOnMock -> null)
+ .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ DeviceConfig.Properties properties = invocationOnMock.getArgument(0);
+ String namespace = properties.getNamespace();
+ // record a wipe
+ if (properties.getKeyset().isEmpty()) {
+ mNamespacesWiped.add(namespace);
+ }
+ return true;
+ }
+ ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class)));
+
+ // Mock PackageWatchdog
+ doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+ .when(() -> PackageWatchdog.getInstance(mMockContext));
+ mockCrashRecoveryProperties(mMockPackageWatchdog);
+
+ doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
+
+ setCrashRecoveryPropRescueBootCount(0);
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testBootLoopNoFlags() {
+ // this is old test where the flag needs to be disabled
+ noteBoot(1);
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(2);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testPersistentAppCrashNoFlags() {
+ // this is old test where the flag needs to be disabled
+ noteAppCrash(1, true);
+ assertTrue(RescueParty.isRebootPropertySet());
+
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteAppCrash(2, true);
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testIsRecoveryTriggeredReboot() {
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(LEVEL_FACTORY_RESET + 1);
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() {
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ int mitigationCount = LEVEL_FACTORY_RESET + 1;
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ setCrashRecoveryPropAttemptingReboot(false);
+ noteBoot(mitigationCount + 1);
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testThrottlingOnBootFailures() {
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1);
+ setCrashRecoveryPropLastFactoryReset(beforeTimeout);
+ for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertFalse(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testThrottlingOnAppCrash() {
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long beforeTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN - 1);
+ setCrashRecoveryPropLastFactoryReset(beforeTimeout);
+ for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertFalse(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testNotThrottlingAfterTimeoutOnBootFailures() {
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1);
+ setCrashRecoveryPropLastFactoryReset(afterTimeout);
+ for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i);
+ }
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testNotThrottlingAfterTimeoutOnAppCrash() {
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ setCrashRecoveryPropAttemptingReboot(false);
+ long now = System.currentTimeMillis();
+ long afterTimeout = now - TimeUnit.MINUTES.toMillis(
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN + 1);
+ setCrashRecoveryPropLastFactoryReset(afterTimeout);
+ for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+ noteAppCrash(i + 1, true);
+ }
+ assertTrue(RescueParty.isRecoveryTriggeredReboot());
+ }
+
+ @Test
+ public void testExplicitlyEnablingAndDisablingRescue() {
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ MITIGATION_RESULT_SKIPPED);
+
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ MITIGATION_RESULT_SUCCESS);
+ }
+
+ @Test
+ public void testDisablingRescueByDeviceConfigFlag() {
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
+
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ MITIGATION_RESULT_SKIPPED);
+
+ // Restore the property value initialized in SetUp()
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
+ }
+
+ @Test
+ public void testDisablingFactoryResetByDeviceConfigFlag() {
+ SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
+
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+
+ // Restore the property value initialized in SetUp()
+ SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
+ }
+
+ @Test
+ public void testHealthCheckLevelsNoFlags() {
+ // this is old test where the flag needs to be disabled
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ // Ensure that no action is taken for cases where the failure reason is unknown
+ assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+
+ // Ensure the correct user impact is returned for each mitigation count.
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+ }
+
+ @Test
+ public void testBootLoopLevelsNoFlags() {
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
+ }
+
+
+ private void noteBoot(int mitigationCount) {
+ RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount);
+ }
+
+ private void noteAppCrash(int mitigationCount, boolean isPersistent) {
+ String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
+ RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+ new VersionedPackage(packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH,
+ mitigationCount);
+ }
+
+ // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions
+ private void mockCrashRecoveryProperties(PackageWatchdog watchdog) {
+ // mock properties in RescueParty
+ try {
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.attempting_factory_reset", "false");
+ return Boolean.parseBoolean(storedValue);
+ }).when(() -> RescueParty.isFactoryResetPropertySet());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ boolean value = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset",
+ Boolean.toString(value));
+ return null;
+ }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.attempting_reboot", "false");
+ return Boolean.parseBoolean(storedValue);
+ }).when(() -> RescueParty.isRebootPropertySet());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ boolean value = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropAttemptingReboot(value);
+ return null;
+ }).when(() -> RescueParty.setRebootProperty(anyBoolean()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("persist.crashrecovery.last_factory_reset", "0");
+ return Long.parseLong(storedValue);
+ }).when(() -> RescueParty.getLastFactoryResetTimeMs());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long value = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropLastFactoryReset(value);
+ return null;
+ }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.max_rescue_level_attempted", "0");
+ return Integer.parseInt(storedValue);
+ }).when(() -> RescueParty.getMaxRescueLevelAttempted());
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int value = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted",
+ Integer.toString(value));
+ return null;
+ }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt()));
+
+ } catch (Exception e) {
+ // tests will fail, just printing the error
+ System.out.println("Error while mocking crashrecovery properties " + e.getMessage());
+ }
+
+ // mock properties in BootThreshold
+ try {
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
+ mCrashRecoveryPropertiesMap = new HashMap<>();
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.rescue_boot_count", "0");
+ return Integer.parseInt(storedValue);
+ }).when(mSpyBootThreshold).getCount();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int count = invocationOnMock.getArgument(0);
+ setCrashRecoveryPropRescueBootCount(count);
+ return null;
+ }).when(mSpyBootThreshold).setCount(anyInt());
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.boot_mitigation_count", "0");
+ return Integer.parseInt(storedValue);
+ }).when(mSpyBootThreshold).getMitigationCount();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ int count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count",
+ Integer.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setMitigationCount(anyInt());
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.rescue_boot_start", "0");
+ return Long.parseLong(storedValue);
+ }).when(mSpyBootThreshold).getStart();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start",
+ Long.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setStart(anyLong());
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String storedValue = mCrashRecoveryPropertiesMap
+ .getOrDefault("crashrecovery.boot_mitigation_start", "0");
+ return Long.parseLong(storedValue);
+ }).when(mSpyBootThreshold).getMitigationStart();
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ long count = invocationOnMock.getArgument(0);
+ mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start",
+ Long.toString(count));
+ return null;
+ }).when(mSpyBootThreshold).setMitigationStart(anyLong());
+
+ Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold");
+ mBootThresholdField.setAccessible(true);
+ mBootThresholdField.set(watchdog, mSpyBootThreshold);
+ } catch (Exception e) {
+ // tests will fail, just printing the error
+ System.out.println("Error while spying BootThreshold " + e.getMessage());
+ }
+ }
+
+ private void setCrashRecoveryPropRescueBootCount(int count) {
+ mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count",
+ Integer.toString(count));
+ }
+
+ private void setCrashRecoveryPropAttemptingReboot(boolean value) {
+ mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot",
+ Boolean.toString(value));
+ }
+
+ private void setCrashRecoveryPropLastFactoryReset(long value) {
+ mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset",
+ Long.toString(value));
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index 0c3c7e2af6f2..e868a6cc5a80 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -34,7 +34,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
/**
* Test for testing revokeTrust & grantTrust for non-renewable trust.
@@ -120,7 +120,7 @@ class GrantAndRevokeTrustTest {
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback)
await()
- verifyZeroInteractions(callback)
+ verifyNoMoreInteractions(callback)
}
companion object {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 51d57f0a0de9..cc7eebca1d00 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -24,15 +24,20 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.usb.IUsbManagerInternal;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.flags.Flags;
import android.hardware.usb.UsbPort;
@@ -70,7 +75,9 @@ public class UsbServiceTest {
@Mock
private IUsbOperationInternal mCallback;
- private static final String TEST_PORT_ID = "123";
+ private static final String TEST_PORT_ID = "1";
+
+ private static final String TEST_PORT_ID_2 = "2";
private static final int TEST_TRANSACTION_ID = 1;
@@ -84,7 +91,7 @@ public class UsbServiceTest {
private UsbService mUsbService;
- private UsbManagerInternal mUsbManagerInternal;
+ private IUsbManagerInternal mIUsbManagerInternal;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -101,9 +108,9 @@ public class UsbServiceTest {
mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
mUserManager, mUsbSettingsManager);
- mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
- assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
- .that(mUsbManagerInternal).isNotNull();
+ mIUsbManagerInternal = LocalServices.getService(IUsbManagerInternal.class);
+ assertWithMessage("LocalServices.getService(IUsbManagerInternal.class)")
+ .that(mIUsbManagerInternal).isNotNull();
}
private void assertToggleUsbSuccessfully(int requester, boolean enable,
@@ -113,7 +120,7 @@ public class UsbServiceTest {
verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
enable, TEST_TRANSACTION_ID, mCallback, null);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
clearInvocations(mUsbPortManager);
clearInvocations(mCallback);
@@ -124,7 +131,7 @@ public class UsbServiceTest {
assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
- verifyZeroInteractions(mUsbPortManager);
+ verifyNoMoreInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
clearInvocations(mUsbPortManager);
@@ -181,7 +188,7 @@ public class UsbServiceTest {
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, TEST_SECOND_CALLER_ID, false);
- verifyZeroInteractions(mUsbPortManager);
+ verifyNoMoreInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
}
@@ -196,7 +203,7 @@ public class UsbServiceTest {
verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, null);
- verifyZeroInteractions(mCallback);
+ verifyNoMoreInteractions(mCallback);
}
/**
@@ -255,30 +262,42 @@ public class UsbServiceTest {
assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
}
- /**
- * Verify USB Manager internal calls mPortManager to get UsbPorts
- */
@Test
- public void usbManagerInternal_getPorts_callsPortManager() {
- when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
-
- UsbPort[] ports = mUsbManagerInternal.getPorts();
-
- verify(mUsbPortManager).getPorts();
- assertEquals(ports.length, 0);
+ public void usbManagerInternal_enableUsbDataSignal_successfullyEnabled() {
+ assertTrue(runInternalUsbDataSignalTest(true, true, true));
}
@Test
- public void usbManagerInternal_enableUsbData_successfullyEnable() {
- boolean desiredEnableState = true;
+ public void usbManagerInternal_enableUsbDataSignal_successfullyDisabled() {
+ assertTrue(runInternalUsbDataSignalTest(false, true, true));
+ }
- assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
- TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+ @Test
+ public void usbManagerInternal_enableUsbDataSignal_returnsFalseIfOnePortFails() {
+ assertFalse(runInternalUsbDataSignalTest(true, true, false));
+ }
- verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
- desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
- verifyZeroInteractions(mCallback);
- clearInvocations(mUsbPortManager);
- clearInvocations(mCallback);
+ private boolean runInternalUsbDataSignalTest(boolean desiredEnableState, boolean portOneSuccess,
+ boolean portTwoSuccess) {
+ UsbPort port = mock(UsbPort.class);
+ UsbPort port2 = mock(UsbPort.class);
+ when(port.getId()).thenReturn(TEST_PORT_ID);
+ when(port2.getId()).thenReturn(TEST_PORT_ID_2);
+ when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] { port, port2 });
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID),
+ eq(desiredEnableState), anyInt(), any(IUsbOperationInternal.class), isNull()))
+ .thenReturn(portOneSuccess);
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID_2),
+ eq(desiredEnableState), anyInt(), any(IUsbOperationInternal.class), isNull()))
+ .thenReturn(portTwoSuccess);
+ try {
+ boolean result = mIUsbManagerInternal.enableUsbDataSignal(desiredEnableState,
+ TEST_INTERNAL_REQUESTER_REASON_1);
+ clearInvocations(mUsbPortManager);
+ return result;
+ } catch(RemoteException e) {
+ fail("RemoteException thrown when calling enableUsbDataSignal");
+ return false;
+ }
}
}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 8cd89ce89e83..6b099450064f 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -75,16 +75,7 @@ public class TestableLooper {
* Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
*/
private static boolean isAtLeastBaklava() {
- Method[] methods = TestLooperManager.class.getMethods();
- for (Method method : methods) {
- if (method.getName().equals("peekWhen")) {
- return true;
- }
- }
- return false;
- // TODO(shayba): delete the above, uncomment the below.
- // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
- // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
}
static {
@@ -247,6 +238,36 @@ public class TestableLooper {
while (processQueuedMessages() != 0) ;
}
+ public long peekWhen() {
+ if (isAtLeastBaklava()) {
+ return peekWhenBaklava();
+ } else {
+ return peekWhenLegacy();
+ }
+ }
+
+ private long peekWhenBaklava() {
+ Long when = mQueueWrapper.peekWhen();
+ if (when != null) {
+ return when;
+ } else {
+ return 0;
+ }
+ }
+
+ private long peekWhenLegacy() {
+ try {
+ Message msg = (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(mLooper.getQueue());
+ if (msg != null) {
+ return msg.getWhen();
+ } else {
+ return 0;
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e);
+ }
+ }
+
public void moveTimeForward(long milliSeconds) {
if (isAtLeastBaklava()) {
moveTimeForwardBaklava(milliSeconds);
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index fd5c4caca484..a7e0125eb3d4 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -17,8 +17,8 @@ package android.testing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 4d379e45a81a..bb54a26036db 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -68,16 +68,7 @@ public class TestLooper {
* Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
*/
private static boolean isAtLeastBaklava() {
- Method[] methods = TestLooperManager.class.getMethods();
- for (Method method : methods) {
- if (method.getName().equals("peekWhen")) {
- return true;
- }
- }
- return false;
- // TODO(shayba): delete the above, uncomment the below.
- // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
- // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
}
static {
diff --git a/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java b/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java
index 6205b98d4e79..05f237f0002f 100644
--- a/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java
+++ b/tests/utils/testutils/tests/src/android/os/test/TestLooperTest.java
@@ -19,7 +19,7 @@ package android.os.test;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 6608dda95a4b..a34908051360 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -37,10 +37,10 @@ import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index f6123d29f35a..e1a572e7a481 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -21,7 +21,7 @@ import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 8374fd944568..7f0cabf7d159 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -24,8 +24,8 @@ import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
index 2b92428918db..0185931d628f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
@@ -18,9 +18,9 @@ package com.android.server.vcn;
import static android.net.NetworkProvider.NetworkOfferCallback;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index bd4aeba761da..c12adcbab08a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -31,10 +31,10 @@ import static com.android.server.vcn.Vcn.VcnContentResolver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index 27c1bc105bde..3ca84cf05cd1 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -28,7 +28,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -83,7 +82,7 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
.thenReturn(mIpSecPacketLossDetector);
when(mCarrierConfig.getIntArray(
- eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+ eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), any()))
.thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
@@ -309,7 +308,7 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
public void testSetCarrierConfig() throws Exception {
final int additionalTimeoutMin = 10;
when(mCarrierConfig.getIntArray(
- eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+ eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), any()))
.thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
// Update evaluator and penalize the network
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 0d261abd728d..e51477c668dd 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -249,6 +249,8 @@ struct ResourceFile {
// Flag
std::optional<FeatureFlagAttribute> flag;
+
+ bool uses_readwrite_feature_flags = false;
};
/**
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 5435cba290fc..db7dddc49a99 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -664,6 +664,7 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag)
if (!config_value->value) {
// Resource does not exist, add it now.
config_value->value = std::move(res.value);
+ config_value->uses_readwrite_feature_flags = res.uses_readwrite_feature_flags;
} else {
// When validation is enabled, ensure that a resource cannot have multiple values defined for
// the same configuration unless protected by flags.
@@ -681,12 +682,14 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag)
ConfigKey{&res.config, res.product}, lt_config_key_ref()),
util::make_unique<ResourceConfigValue>(res.config, res.product));
(*it)->value = std::move(res.value);
+ (*it)->uses_readwrite_feature_flags = res.uses_readwrite_feature_flags;
break;
}
case CollisionResult::kTakeNew:
// Take the incoming value.
config_value->value = std::move(res.value);
+ config_value->uses_readwrite_feature_flags = res.uses_readwrite_feature_flags;
break;
case CollisionResult::kConflict:
@@ -843,6 +846,12 @@ NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
return *this;
}
+NewResourceBuilder& NewResourceBuilder::SetUsesReadWriteFeatureFlags(
+ bool uses_readwrite_feature_flags) {
+ res_.uses_readwrite_feature_flags = uses_readwrite_feature_flags;
+ return *this;
+}
+
NewResource NewResourceBuilder::Build() {
return std::move(res_);
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index b0e185536d16..778b43adb50b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -104,6 +104,9 @@ class ResourceConfigValue {
// The actual Value.
std::unique_ptr<Value> value;
+ // Whether the value uses read/write feature flags
+ bool uses_readwrite_feature_flags = false;
+
ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
: config(config), product(product) {
}
@@ -284,6 +287,7 @@ struct NewResource {
std::optional<AllowNew> allow_new;
std::optional<StagedId> staged_id;
bool allow_mangled = false;
+ bool uses_readwrite_feature_flags = false;
};
struct NewResourceBuilder {
@@ -297,6 +301,7 @@ struct NewResourceBuilder {
NewResourceBuilder& SetAllowNew(AllowNew allow_new);
NewResourceBuilder& SetStagedId(StagedId id);
NewResourceBuilder& SetAllowMangled(bool allow_mangled);
+ NewResourceBuilder& SetUsesReadWriteFeatureFlags(bool uses_feature_flags);
NewResource Build();
private:
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 2a7921600477..755dbb6f8e42 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -673,11 +673,13 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
// Update the output format of this XML file.
file_ref->type = XmlFileTypeForOutputFormat(options_.output_format);
- bool result = table->AddResource(NewResourceBuilder(file.name)
- .SetValue(std::move(file_ref), file.config)
- .SetAllowMangled(true)
- .Build(),
- context_->GetDiagnostics());
+ bool result = table->AddResource(
+ NewResourceBuilder(file.name)
+ .SetValue(std::move(file_ref), file.config)
+ .SetAllowMangled(true)
+ .SetUsesReadWriteFeatureFlags(doc->file.uses_readwrite_feature_flags)
+ .Build(),
+ context_->GetDiagnostics());
if (!result) {
return false;
}
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 2e20e8175213..bac871b8bdc3 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -414,6 +414,8 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
.SetId(res_id, OnIdConflict::CREATE_ENTRY)
.SetAllowMangled(true);
+ res_builder.SetUsesReadWriteFeatureFlags(entry->uses_feature_flags());
+
if (entry->flags() & ResTable_entry::FLAG_PUBLIC) {
Visibility visibility{Visibility::Level::kPublic};
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
index 9dc205f4c1ba..0be392164453 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -199,6 +199,10 @@ void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) {
flags |= ResTable_entry::FLAG_WEAK;
}
+ if (entry->uses_readwrite_feature_flags) {
+ flags |= ResTable_entry::FLAG_USES_FEATURE_FLAGS;
+ }
+
if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
flags |= ResTable_entry::FLAG_COMPLEX;
}
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
index c11598ec12f7..f54b29aa8f2a 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.h
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -38,6 +38,8 @@ struct FlatEntry {
// The entry string pool index to the entry's name.
uint32_t entry_key;
+
+ bool uses_readwrite_feature_flags;
};
// Pair of ResTable_entry and Res_value. These pairs are stored sequentially in values buffer.
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 1a82021bce71..50144ae816b6 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -502,7 +502,8 @@ class PackageFlattener {
// Group values by configuration.
for (auto& config_value : entry.values) {
config_to_entry_list_map[config_value->config].push_back(
- FlatEntry{&entry, config_value->value.get(), local_key_index});
+ FlatEntry{&entry, config_value->value.get(), local_key_index,
+ config_value->uses_readwrite_feature_flags});
}
}
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 0f1168514c4a..9156b96b67ec 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -1069,4 +1069,23 @@ TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseInExemption) {
testing::IsTrue());
}
+TEST_F(TableFlattenerTest, UsesReadWriteFeatureFlagSerializesCorrectly) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .Add(NewResourceBuilder("com.app.a:color/foo")
+ .SetValue(util::make_unique<BinaryPrimitive>(
+ uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+ .SetUsesReadWriteFeatureFlags(true)
+ .SetId(0x7f020000)
+ .Build())
+ .Build();
+ ResTable res_table;
+ TableFlattenerOptions options;
+ ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
+
+ uint32_t flags;
+ ASSERT_TRUE(res_table.getResourceEntryFlags(0x7f020000, &flags));
+ ASSERT_EQ(flags, ResTable_entry::FLAG_USES_FEATURE_FLAGS);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index dbef77615515..47a71fe36e9f 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include <regex>
+#include <string>
+
#include "LoadedApk.h"
#include "cmd/Dump.h"
#include "io/StringStream.h"
@@ -183,4 +186,49 @@ TEST_F(FlaggedResourcesTest, ReadWriteFlagInPathFails) {
"Only read only flags may be used with resources: test.package.rwFlag"));
}
+TEST_F(FlaggedResourcesTest, ReadWriteFlagInXmlGetsFlagged) {
+ auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+ auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+ std::string output;
+ DumpChunksToString(loaded_apk.get(), &output);
+
+ // The actual line looks something like:
+ // [ResTable_entry] id: 0x0000 name: layout1 keyIndex: 14 size: 8 flags: 0x0010
+ //
+ // This regex matches that line and captures the name and the flag value for checking.
+ std::regex regex("[0-9a-zA-Z:_\\]\\[ ]+name: ([0-9a-zA-Z]+)[0-9a-zA-Z: ]+flags: (0x\\d{4})");
+ std::smatch match;
+
+ std::stringstream ss(output);
+ std::string line;
+ bool found = false;
+ int fields_flagged = 0;
+ while (std::getline(ss, line)) {
+ bool first_line = false;
+ if (line.contains("config: v36")) {
+ std::getline(ss, line);
+ first_line = true;
+ }
+ if (!line.contains("flags")) {
+ continue;
+ }
+ if (std::regex_search(line, match, regex) && (match.size() == 3)) {
+ unsigned int hex_value;
+ std::stringstream hex_ss;
+ hex_ss << std::hex << match[2];
+ hex_ss >> hex_value;
+ if (hex_value & android::ResTable_entry::FLAG_USES_FEATURE_FLAGS) {
+ fields_flagged++;
+ if (first_line && match[1] == "layout1") {
+ found = true;
+ }
+ }
+ }
+ }
+ ASSERT_TRUE(found) << "No entry for layout1 at v36 with FLAG_USES_FEATURE_FLAGS bit set";
+ // There should only be 1 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1
+ ASSERT_EQ(fields_flagged, 1);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/FlaggedXmlVersioner.cpp b/tools/aapt2/link/FlaggedXmlVersioner.cpp
index 75c6f17dcb51..8a3337c446cb 100644
--- a/tools/aapt2/link/FlaggedXmlVersioner.cpp
+++ b/tools/aapt2/link/FlaggedXmlVersioner.cpp
@@ -66,6 +66,28 @@ class AllDisabledFlagsVisitor : public xml::Visitor {
bool had_flags_ = false;
};
+// An xml visitor that goes through the a doc and determines if any elements are behind a flag.
+class FindFlagsVisitor : public xml::Visitor {
+ public:
+ void Visit(xml::Element* node) override {
+ if (had_flags_) {
+ return;
+ }
+ auto* attr = node->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
+ if (attr != nullptr) {
+ had_flags_ = true;
+ return;
+ }
+ VisitChildren(node);
+ }
+
+ bool HadFlags() const {
+ return had_flags_;
+ }
+
+ bool had_flags_ = false;
+};
+
std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAaptContext* context,
xml::XmlResource* doc) {
std::vector<std::unique_ptr<xml::XmlResource>> docs;
@@ -74,15 +96,20 @@ std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAap
// Support for read/write flags was added in baklava so if the doc will only get used on
// baklava or later we can just return the original doc.
docs.push_back(doc->Clone());
+ FindFlagsVisitor visitor;
+ doc->root->Accept(&visitor);
+ docs.back()->file.uses_readwrite_feature_flags = visitor.HadFlags();
} else {
auto preBaklavaVersion = doc->Clone();
AllDisabledFlagsVisitor visitor;
preBaklavaVersion->root->Accept(&visitor);
+ preBaklavaVersion->file.uses_readwrite_feature_flags = false;
docs.push_back(std::move(preBaklavaVersion));
if (visitor.HadFlags()) {
auto baklavaVersion = doc->Clone();
baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA;
+ baklavaVersion->file.uses_readwrite_feature_flags = true;
docs.push_back(std::move(baklavaVersion));
}
}
diff --git a/tools/codegen/src/com/android/codegen/Debug.kt b/tools/codegen/src/com/android/codegen/Debug.kt
index de3184468540..6423c4f7f6c0 100644
--- a/tools/codegen/src/com/android/codegen/Debug.kt
+++ b/tools/codegen/src/com/android/codegen/Debug.kt
@@ -21,7 +21,7 @@ import com.github.javaparser.ast.Node
fun Node.dump(indent: String = ""): String {
return buildString {
append(indent)
- appendln(dumpOneLineNoChildren())
+ appendLine(dumpOneLineNoChildren())
childNodes.forEach { child ->
append(child.dump(indent + " "))
}
diff --git a/tools/codegen/src/com/android/codegen/FeatureFlag.kt b/tools/codegen/src/com/android/codegen/FeatureFlag.kt
index 24150d637a7b..f305429f516f 100644
--- a/tools/codegen/src/com/android/codegen/FeatureFlag.kt
+++ b/tools/codegen/src/com/android/codegen/FeatureFlag.kt
@@ -22,6 +22,6 @@ enum class FeatureFlag(val onByDefault: Boolean, val desc: String = "") {
CONST_DEFS(true, "@Int/StringDef's based on declared static constants"),
FOR_EACH_FIELD(false, "forEachField((name, value) -> ...)");
- val kebabCase = name.toLowerCase().replace("_", "-")
- val upperCamelCase = name.split("_").map { it.toLowerCase().capitalize() }.joinToString("")
+ val kebabCase = name.lowercase().replace("_", "-")
+ val upperCamelCase = name.split("_").map { it.lowercase().capitalize() }.joinToString("")
}
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index cc3a15654956..ca04f1eb9ab7 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -126,7 +126,7 @@ class FileInfo(
+"\n}"
}
// Print general code as-is
- is CodeChunk.Code -> chunk.lines.forEach { stringBuilder.appendln(it) }
+ is CodeChunk.Code -> chunk.lines.forEach { stringBuilder.appendLine(it) }
// Recursively render data classes
is CodeChunk.DataClass -> chunk.chunks.forEach { print(it) }
}
@@ -175,11 +175,11 @@ class FileInfo(
/** Debug info */
override fun toString(): String {
return buildString {
- appendln("class $name $range")
+ appendLine("class $name $range")
nested.forEach {
- appendln(it)
+ appendLine(it)
}
- appendln("end $name")
+ appendLine("end $name")
}
}
}
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index d3a8b033dfff..710960249661 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -43,7 +43,7 @@ fun ClassPrinter.generateConstDef(consts: List<Pair<VariableDeclarator, FieldDec
}
var AnnotationName = prefix.split("_")
.filterNot { it.isBlank() }
- .map { it.toLowerCase().capitalize() }
+ .map { it.lowercase().capitalize() }
.joinToString("")
val annotatedConst = consts.find { it.second.annotations.isNonEmpty }
if (annotatedConst != null) {
@@ -122,7 +122,7 @@ fun FileInfo.generateAidl() {
if (aidl.exists()) return
aidl.writeText(buildString {
sourceLines.dropLastWhile { !it.startsWith("package ") }.forEach {
- appendln(it)
+ appendLine(it)
}
append("\nparcelable ${mainClass.nameAsString};\n")
})
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index a40bdd7ba8e1..96314435d1b7 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -62,7 +62,7 @@ fun String.toLowerCamel(): String {
if (length >= 2 && this[0] == 'm' && this[1].isUpperCase()) return substring(1).capitalize()
if (all { it.isLetterOrDigit() }) return decapitalize()
return split("[^a-zA-Z0-9]".toRegex())
- .map { it.toLowerCase().capitalize() }
+ .map { it.lowercase().capitalize() }
.joinToString("")
.decapitalize()
}
diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp
index f8629f9bd0b8..07caa9a979d9 100644
--- a/tools/fonts/Android.bp
+++ b/tools/fonts/Android.bp
@@ -23,11 +23,6 @@ package {
python_defaults {
name: "fonts_python_defaults",
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}
python_binary_host {
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
index ddacf57c3a3e..43f21221ae5a 100644
--- a/tools/lint/fix/Android.bp
+++ b/tools/lint/fix/Android.bp
@@ -25,11 +25,6 @@ python_binary_host {
name: "lint_fix",
main: "soong_lint_fix.py",
srcs: ["soong_lint_fix.py"],
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}
python_library_host {
diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp
index 05ba405d2c52..f88709375c98 100644
--- a/tools/lint/global/integration_tests/Android.bp
+++ b/tools/lint/global/integration_tests/Android.bp
@@ -65,9 +65,4 @@ python_test_host {
"AndroidGlobalLintTestNoAidl_py",
"AndroidGlobalLintTestMissingAnnotation_py",
],
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}
diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
index 4995eebdd79e..fe72dae9ff34 100644
--- a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
+++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
@@ -155,35 +155,35 @@ class IntDefProcessor : AbstractProcessor() {
) {
val indent = " "
- writer.appendln("{")
+ writer.appendLine("{")
val intDefTypesCount = annotationTypeToIntDefMapping.size
var currentIntDefTypesCount = 0
for ((field, intDefMapping) in annotationTypeToIntDefMapping) {
- writer.appendln("""$indent"$field": {""")
+ writer.appendLine("""$indent"$field": {""")
// Start IntDef
- writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""")
+ writer.appendLine("""$indent$indent"flag": ${intDefMapping.flag},""")
- writer.appendln("""$indent$indent"values": {""")
+ writer.appendLine("""$indent$indent"values": {""")
intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) ->
"""$indent$indent$indent"$value": "$identifier""""
}
- writer.appendln()
- writer.appendln("$indent$indent}")
+ writer.appendLine()
+ writer.appendLine("$indent$indent}")
// End IntDef
writer.append("$indent}")
if (++currentIntDefTypesCount < intDefTypesCount) {
- writer.appendln(",")
+ writer.appendLine(",")
} else {
- writer.appendln("")
+ writer.appendLine("")
}
}
- writer.appendln("}")
+ writer.appendLine("}")
}
}
}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 02da835d1002..c30e7bfdfa50 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -23,7 +23,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doNothing;